flutter TabBarView sometimes not triggering on gesture swipe - flutter

I am using Flutter -Android studio. I have main screen with TabBarView control (2 pages). each page get data from sqfite database. both have same statefull widget class, but I pass parameter to look into database and display.
Issue : when I click tabbar header , data displayed is Ok. But when I swipe tabs, it sometimes work and sometimes does not work. as per below video. I have check and seems that Build event is not trigger every if Swipe was done.
Main widget with tabbarview
screen record
Note that 1st tab has only 1 record, while second has 6 records. tabClick works fine, but swipe sometime not work properly.
body: TabBarView(
controller: _tabController,
children: [
DisplayTransactions(
tmptransType: TransactionType.enIncome,
transacBloc: _transacBloc,
SelectedItemsCount: bRowSelectionCount,
onSelectionChanged: (count) {
setState(() {
bRowSelectionCount = count;
});
},
),
// Center(child: Text("Page 1")),
DisplayTransactions(
tmptransType: TransactionType.enExpense,
transacBloc: _transacBloc,
SelectedItemsCount: bRowSelectionCount,
onSelectionChanged: (count) {
setState(() {
bRowSelectionCount = count;
});
},
//Center(child: Text("Page 2")),
)
],
),
------ > Widget to display data into listview for each tabbar page
class DisplayTransactions extends StatefulWidget {
final TransactionType tmptransType;
final FinanceTransBlock transacBloc;
final Function onSelectionChanged;
int SelectedItemsCount;
/// -1 expense 1 income
DisplayTransactions(
{Key key,
#required this.tmptransType,
#required this.transacBloc,
this.SelectedItemsCount,
this.onSelectionChanged})
: super(key: key);
#override
_DisplayTransactionsState createState() => _DisplayTransactionsState();
}
class _DisplayTransactionsState extends State<DisplayTransactions> {
var isSelected = false;
var mycolor = Colors.white;
// int iselectionCount = 0;
#override
Widget build(BuildContext context) {
widget.transacBloc.transacType = widget.tmptransType;
debugPrint("----------------------------- ${widget.tmptransType}");
if (widget.SelectedItemsCount == 0) {// if no rows selected, then reload database based on trans type, eg expense, or income.. etc
widget.transacBloc.refresh();
}
return StreamBuilder<List<FinanceTransaction>>(
stream: widget.transacBloc.transacations,
builder: (context, snapshot) {
if (snapshot.hasData) {
List<FinanceTransaction> list = snapshot.data;
// return buildTaskListWal(snapshot.data);
return Padding(

It looks like the app is taking some time to retrieve the new data. Try checking for null on the snapshot.data and display a circularProgressIndicator.
if (snapshot.data == null) {
return CirgularProgressIndicator();
} else {
//display data
}

Related

emitting of state doesn't seem to work the second time , flutter_bloc

I am working with flutter_bloc and trying to understand how it works entirely.
I have Profile Screen. Where in the User should enter his details if previously not existed else should update the details.
Logic: If the user already exists then i fill up the textfields prior to loading , else the textfields are left blank
Problem: I have worked out of achieving the above mentioned goal , but everything seems to work only the first time , Once i save the profile and come back to the profile page it doesn't load the data and textfields are empty even if the user exists.
User_Cubit
class UserCubit extends Cubit<UserState> {
UserCubit() : super(UserInitialState()) {
checkIfUserExists();
}
void checkIfUserExists() {
emit(UserLoadingState());
...
if (userExists) {
emit(UserExists(user));
} else {
emit(UserNotExists());
}
});
}
Profile Screen
class _MyProfileScreenState extends State<MyProfileScreen> {
TextEditingController? fullNameController;
TextEditingController? mailAddressController;
late UserCubit _userCubit;
#override
void initState() {
fullNameController = TextEditingController();
mailAddressController = TextEditingController();
_userCubit = UserCubit(); // initializing the cubit here
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: MultiBlocListener(
listeners: [
BlocListener<UserCubit, UserState>(
listener: (context, state) {
if (state is UserExists) {
appUser = state.user;
mailAddressController!.text = appUser!.email; // Loading the fields
fullNameController!.text = appUser!.fullName; // Loading the fields
}
},
)
],
child: BlocBuilder<UserCubit, UserState>(
builder: (context, state) {
if (state is UserLoadingState) {
return const Center(child: CircularProgressIndicator());
}
return Container(
TextFormField() // For fullName
TextFormField() // For EmailAddress
)
)
);
}
Why does this functionality work only the first time not the consecutive times. Thougth the UserCubit is intialized in initstate ?
Any further suggestions to improve this logic by not initializing the UserCubit on every page render would be also appreciated !!

I try using Provider read methods to add desired stuff but nothing works

I have 2 models extending ChangeNotifier called Tabs and Views. Tabs for tabs parameter of TabBar an Views for TabBarView.
models:
Tabs.dart
class Tabs with ChangeNotifier {
final List<Widget> _tabs = [Text('Today')];
List<Widget> get tabs => _tabs;
void addTabs(String text) {
_tabs.add(Tab(text: text));
notifyListeners();
}
void removetabs(String text) {
_tabs.remove(Tab(text: text));
notifyListeners();
}
}
Views.dart
class Views with ChangeNotifier {
final List<Widget> _views = [Container(color: Colors.red, height: 100, width: 100,)];
List<Widget> get views => _views;
void addView(Widget widget) {
_views.add(widget);
notifyListeners();
}
}
main.dart
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MultiProvider(providers: [
ChangeNotifierProvider<Tabs>(create: (_) => Tabs()),
ChangeNotifierProvider<Views>(create: (_) => Views()),
],
builder:(context, child)=> MaterialApp(.......);
So for both TabBar and TabBarView I watch or listen to the List tabs and views model variables and that works perfectly fine as I tried adding hard coded widgets to these 2 variables.
However, Provider read method doesn't work when I try adding a tab or view.
final tabs = Tabs().tabs; final views = Views().views;
AppBar(bottom: tabs.isNotEmpty
? TabBar(
indicatorColor: Colors.white,
tabs: context.watch<Tabs>().tabs,
)
: null),
body: tabs.isNotEmpty
? TabBarView(children: context.watch<Views>().views)
: appLogo,
adding tabs and views
So in my app I first query firebase docs and if there isn't any doc I want to add one with the date 'Today' and then add to the tabs and views model variable that I created but nothing get added to these 2 lists.
Future addTabsViews() async {
try{
QuerySnapshot<Map<String, dynamic>> query = await todosRef.limit(1).get(const GetOptions(source: Source.server));
if (query.docs.isEmpty) {
Map<String, dynamic> todayDoc = {
'todo': null,
'subTask': null,
'isChecked': null,
'date': 'Today'
};
await todosRef.add(todayDoc);
context.read<Tabs>().addTabs(todayDoc['date']);
context.read<Views>().addView(Container());
print(tabs);
print(views);
} } on FirebaseException catch (e){
print(e.toString());
}
}
I call this future in the initState;
Been stuck in this error for days and can't fix it until now.
When you create your tabs and views that you reference like this, you are creating new objects and not referencing the same that you use in your provider.
final tabs = Tabs().tabs;
final views = Views().views;
To get the Tabs instance from the provider and listen to any updates you have to get it like this e.g.:
var tabs = Provider.of(context).tabs;
You can now use tabs from the provider and they will update when you notifyListeners();
Widget build(BuildContext context) {
var tabs = Provider.of<Tabs>(context).tabs;
var views = Provider.of<Views>(context).views;
return Scaffold(
appBar: AppBar(bottom: tabs.isNotEmpty ? TabBar(tabs: tabs) : null),
body: tabs.isNotEmpty ? TabBarView(children: views) : FlutterLogo(),
);
}

shared preferences does not save radio button checkmark in Flutter

I implemented the shared preferences package in my Flutter app, with a list widget as radio button, that only save the language preference and not the checkmark.
So when i close the Language screen and come back, the language checkmark goes the the default one even if the language, saved in shared preferences is French or Italian.
This is my Language screen:
class LanguagesScreen extends StatefulWidget {
const LanguagesScreen({Key? key}) : super(key: key);
#override
State<LanguagesScreen> createState() => _LanguagesScreenState();
}
class Item {
final String prefix;
final String? helper;
const Item({required this.prefix, this.helper});
}
var items = [
Item(prefix: 'English', helper: 'English',), //value: 'English'
Item(prefix: 'Français', helper: 'French'),
Item(prefix: 'Italiano', helper: 'Italian'),
];
class _LanguagesScreenState extends State<LanguagesScreen> {
var _selectedIndex = 0;
final _userPref = UserPreferences();
var _selecLangIndex;
int index = 0;
final List<String> entries = <String>['English', 'French', 'Italian'];*/
//init shared preferences
#override
void initState() {
super .initState();
_populateField();
}
void _populateField() async {
var prefSettings = await _userPref.getPrefSettings();
setState((){
_selecLangIndex = prefSettings.language;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(...
),
body: CupertinoPageScaffold(
child: Container(
child: SingleChildScrollView(
child: CupertinoFormSection.insetGrouped(
children: [
...List.generate(items.length, (index) => GestureDetector(
onTap: () async {
setState(() => _selectedIndex = index);
if (index == 0){
await context.setLocale(Locale('en','US'));
_selecIndex = Language.English;
}
else if (index == 1){
await context.setLocale(Locale('fr','FR'));
_selecIndex = Language.French;
}
child: buildCupertinoFormRow(
items[index].prefix,
items[index].helper,
selected: _selectedIndex == index,
)
)),
TextButton(onPressed:
_saveSettings,
child: Text('save',
)
buildCupertinoFormRow(String prefix, String? helper, {bool selected = false,}) {
return CupertinoFormRow(
prefix: Text(prefix),
helper: helper != null
? Text(helper, style: Theme.of(context).textTheme.bodySmall,)
:null, child: selected ? const Icon(CupertinoIcons.check_mark,
color: Colors.blue, size: 20,) :Container(),
);
}
void _saveSettings() {
final newSettings = PrefSettings(language:_selecIndex);
_userPref.saveSettings(newSettings);
Navigator.pop(context);
}
}
this is the UserPreference:
class UserPreferences {
Future saveSettings(PrefSettings prefSettings) async {
final preferences = await SharedPreferences.getInstance();
await preferences.setInt('language' , prefSettings.language.index );
}
Future<PrefSettings> getPrefSettings() async {
final preferences = await SharedPreferences.getInstance();
final language = Language.values[preferences.getInt('language') ?? 0 ];
return PrefSettings(language: language);
}
}
enum Language { English, French, Italian}
class PrefSettings{
final Language language;
PrefSettings (
{required this.language});
}
I'm betting that the issue is in initState. You are calling _populateField, but it doesn't complete before building because it's an async method, and you can't await for it: so the widget gets build, loading the default position for the checkmark, and only after that _populateField completes...but then it's too late to show the saved data correctly.
In my experience, if I have not already instantiated a SharedPreferences object somewhere else in the code, I use this to load it:
class _LanguagesScreenState extends State<LanguagesScreen> {
[...]
#override
Widget build(BuildContext context) {
return FutureBuilder(
//you can put any async method here, just be
//sure that you use the type it returns later when using 'snapshot.data as T'
future: await SharedPreferences.getInstance(),
builder: (context, snapshot) {
//error handling
if (!snapshot.hasData || snapshot.connectionState != ConnectionState.done) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text(snapshot.error.toString()));
}
var prefs= snapshot.data as SharedPreferences;
//now you have all the preferences available without need to await for them
return Scaffold((
[...]
);
EDIT
I started writing another comment, but there are so many options here that there wasn't enough space.
First, the code I posted should go in your _LanguagesScreenState build method. The FutureBuilder I suggested should wrap anything that depends on the Future you must wait for to complete. I put it up at the root, above Scaffold, but you can move it down the widgets' tree as you need, just remember that everything that needs to read the preferences has to be inside the FutureBuilder.
Second, regarding SharedPreferences.getInstance(), there are two ways: the first is declaring it as a global variable, and loading it even in the main method where everything starts. By doing this you'll be able to reference it from anywhere in your code, just be careful to save the changes everytime is needed. The second is to load it everytime you need, but you'll end up using a FutureBuilder a lot. I don't know if any of these two options is better than the other: the first might have problems if somehow the SharedPreferences object gets lost, while the second requires quite more code to work.

State and Scroll position restore Flutter

I have an app which fetches posts from a site using a API and then displays it. There are three navigation options, which are basically filters.
The problem is, whenever I switch to another navigation tab (I'm using bottom navigation bar), it ends up rebuilding the whole page, meaning it will fetch all that data again and it might potentially contain new data.
What I want to do is to keep restore this data in a way that is fast and my initState() doesn't get called(because that is what fetches the data). I did try using all the different kind of keys but I cant get it to work.
Main page:
class AppHomePage extends StatefulWidget {
AppHomePage({Key? key}) : super(key: key);
#override
_AppHomePageState createState() => _AppHomePageState();
}
List<Widget> _navs = [
BestPostsRoute(key: PageStorageKey("bestP")),
HotPostsRoute(key: PageStorageKey("hotP")),
NewPostsRoute(key: PageStorageKey("newP"))
];
class _AppHomePageState extends State<AppHomePage> {
int _currentIndex = 0;
onTap(index) => {
setState(() => {_currentIndex = index})
};
#override
Widget build(BuildContext context) {
return Scaffold(
/* appbar ... */
body: _navs.elementAt(_currentIndex),
bottomNavigationBar: BottomNavigationBar(
items: [
/* nav items */
],
currentIndex: _currentIndex,
onTap: onTap,
),
);
}
}
One of the three pages(the code is similar in all three):
/* imports... */
class HotPostsRoute extends StatefulWidget {
HotPostsRoute({Key? key}) : super(key: key);
#override
_HotPostsRouteState createState() => _HotPostsRouteState();
}
class _HotPostsRouteState extends State<HotPostsRoute> {
late PostInstance postInstance;
List<Post> _posts = [];
bool _loaded = false;
fetchPosts(String? after) async {
var stream = postInstance.front.hot(limit: 10, after: after);
await for (UserContent post in stream) {
Submission submission = post as Submission;
Post pPost = Post(submission);
pPost.parse().then((value) => setState(() {
_posts.add(pPost);
}));
}
setState(() {
_loaded = true;
});
}
#override
void initState() {
super.initState();
if (mounted) {
setState(() {
redditInstance =
Provider.of<PostInstanceState>(context, listen: false)
.getInstance;
});
fetchPosts("");
}
}
// Fetches and generates posts
Widget _buildPosts() {
return ListView.builder(
itemCount: _posts.length + 1,
itemBuilder: (ctx, index) {
if (index < _posts.length) {
return _buildPost(_posts.elementAt(index));
} else {
fetchPosts(_posts.last.fullname);
return SpinKitDualRing(color: Colors.white);
}
},
);
}
// A singular post
Widget _buildPost(Post post) {
print(post.object);
return PostCard(post, key: ObjectKey(post.object)); // .object just creates a map of all fields
}
#override
Widget build(BuildContext context) {
setState(() {});
return Container(
child: _loaded ? _buildPosts() : SpinKitDualRing(color: Colors.white),
);
}
}
So I kept searching and eventually a post on Medium led me to the IndexedStack Widget.
Its a widget that is made from the Stack widget and basically loads and stores the state of all its childrens. Unlike Stack, it shows its children one at a time and thus is perfect to use with BottomNavigationBar.
Here's the Blog post for anyone looking out.

Widgets with future builder not removing widget after provider was updated with async

I have been learning flutter for 2-3 months now and I feel I have a reached a fundamental roadblock with understanding state management. This post will be long unfortunately so please bare with me and I hope I put the right detail.
Problem Definition
I have a list of widgets in a shopping cart,im at the point where I click minus and it only has 1 left the widget must be removed.No matter what I try I cant get that widget to be removed. If I click back button and go back into cart the Item will not appear anymore.
I have considered other methods, like disposing the widget(that didn't seem to work) and I was busy implementing Visibility Show/hide widgets in Flutter programmatically
but that doesn't feel like the right way.If my understanding of providers,changeNotifiers,async and future builders,is correct the below method should work and I think its fundamental to my flutter journey to understand why it doesn't work.
Overview:The idea was to use the minus button on CartItemWidget to call a method that updates Json stored on the local device, then repopulate the List cartProdList in ProductProvider which calls
notifyListeners() and then should propagate everywhere the provider is used. Now I have used this pattern successfully 5 times now, the only different this time is it will be removing a widget which I haven't done before. But this should work dynamically if the future is based of the same provider right ?
function call order
CartItemWidget.onPressed:()
calls >>>
ProductProvider.cartMinusOne(String id)
calls >>>
ProductProvider.Future<List<Product>> cartProducts()
well here goes the code.I also wouldn't mind comments on things I could be doing better in all areas.
CartWidget
class CartWidget extends StatefulWidget {
#override
_CartWidgetState createState() => _CartWidgetState();
}
class _CartWidgetState extends State<CartWidget> {
var providerOfProd;
ProductProvider cartProdProvider = new ProductProvider();
#override
void initState() {
_productsList = new ProductsList();
super.initState();
providerOfProd = Provider.of<ProductProvider>(context, listen: false).cartProducts();
}
#override
Widget build(BuildContext context) {
........
Column(children: <Widget>[
FutureBuilder(
future: providerOfProd,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Container(
width: 0,
height: 0,
);
case ConnectionState.done:
return ListView.separated(
..............
},
itemBuilder: (context, index) {
return CartItemWidget(
product: cartProdProvider.cartProdList.elementAt(index),
heroTag: 'cart',
quantity: cartProdProvider.cartProdList.elementAt(index).cartqty,
key: UniqueKey(),
);
},
);
.........
CartItemWidget
class CartItemWidget extends StatefulWidget {
CartItemWidget({Key key, this.product, this.heroTag, this.quantity = 1}) : super(key: key);
// ProductProvider cartProd = new ProductProvider();
String heroTag;
Product product;
int quantity;
#override
_CartItemWidgetState createState() => _CartItemWidgetState();
}
class _CartItemWidgetState extends State<CartItemWidget> {
#override
Widget build(BuildContext context) {
return Consumer<ProductProvider>(
builder: (context, productProv, _) => InkWell(
child: Container(
.............
child: Row(
children: <Widget>[
.............
IconButton(
onPressed: () {
setState(() {
productProv.cartMinusOne(widget.product.id);
widget.quantity = this.decrementQuantity(widget.quantity);
});
}
.............
ProductProvider
class ProductProvider with ChangeNotifier {
ProductProvider() {
cartProducts();
}
List<Product> cartProdList;
cartMinusOne(String id) async {
//Code to minus item,then return as a string to save as local jason
var test = jsonEncode(cartList);
saveLocalJson(test, 'cart.json');
cartProducts();
notifyListeners();
}
Future<List<Product>> cartProducts() async {
String jsonString = await JsonProvider().getProductJson();
String cartString = await getCartJson();
var filterProdList = (json.decode(jsonString) as List).map((i) => Product.fromJson(i)).toList();
//code to get match cart list to product list
cartProdList = filterProdList.where((element) => element.cartqty > 0).toList();
notifyListeners();
return cartProdList;
}
........................