I use flutter with firebase auth, and I try to use streamBuilder with the onAuthStateChanged from Firebase Auth.
If user is signedIn I return the homeScreen and in others cases the signInScreen.
Everything is working great, but when I add a print('') in the streambuilder (just before the if for example) I can see that my streamBuilder is called too many times, for example when I push a new screen, when I pop a screen ...etc.. even if there is no data (just an empty Container()) in these screens. At least onAuthStateChanged is called twice by any of my action (push, pop...) with connection.waiting state and connection.active.
Because of that my homeScreen is rebuilt each time, that mean my whole application is rebuilt each time.
The decision code :
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: userBloc.currentUser.onStateChange(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return HomeScreen();
} else {
return SignInScreen();
}
},
),
);
}
My main goal is to force streamBuilder to rebuild widget only if snapshot.hasData change, and not when onAuthStateChanged receive something not useful and doesn't need to rebuild my whole app.
Related
I have tasks list from flutter_downloader but that list is not enough to show in list. I need to show other information in list view as well as download information.
I already got the download tasks in initial state but I need to wait to get another list from bloc. after DownloadedSongListLoaded, I want to call _combineList(favouriteSongs); But I only want to return the Container after _combineList(favouriteSongs) finished.
So, how can I call async function in widget in BlocBuilder or other way around.
child: BlocBuilder<FavouriteSongBloc, FavouriteSongState>(
builder: (context, state) {
if (state is FavouriteSongError) {
return SomethingWentWrongScreen(error: state.error);
} else if (state is DownloadedSongListLoaded) {
favouriteSongs = state.favouriteSongs;
await _combineList(favouriteSongs); <== here, I want to manipulate the favouriteSongs list before binding the below Container widget.
return const Container() //ListView builder will be here
}
return const CircularProgressIndicatorWidget();
},
)
I'm having an issue of the widget subtree constantly rebuilding from inside the ValueListenableBuilder. It is supposed to run a rebuild on change, and in this case it is listening to a table on a Flutter Hive Database.
Things I've tired:
I had all of my Hive Boxes open in the main method, so that I have access to each box from anywhere in the app. I tired only opening the Hive box when something is changed, then promptly closing this box. Didn't work
Things I think it could be, but not sure:
Mixing ChangeNotifierProvider with the ValueListenableBuilder - Because some of the subtree also utilizes changenotifier, but with ValueListenableBuilder constantly rebuilding the subtree, any changes I pass into the provider get wiped out.
Is there anyway of only rebuilding on a change only?
#override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable:
Hive.box<Manifest>(HiveTables.manifestBox).listenable(),
child: assignmentWidgets,
builder: (context, Box<Manifest> manifestBox, child) {
if (manifestBox.isNotEmpty)
return child!;
},
);
}
the Hive provides listening to a whole Box, so every time something happens inside that Box, the builder will be called :
ValueListenableBuilder<Box>(
valueListenable: Hive.box('settings').listenable(),
builder: (context, box, widget) {
// build widget
},
),
but you can also specify a List<String> of keys that you want only them to be listenable by the ValueListenableBuilder with the keys property like this:
ValueListenableBuilder<Box>(
valueListenable: Hive.box('settings').listenable(keys: ['firstKey', 'secondKey']),
builder: (context, box, widget) {
// build widget
},
),
now for keys other than firstKey and secondKey, every operation that's executed over them will not update the ValueListenableWidget, but operation on firstKey and secondKey will update it only.
to demonstrate the problem, let me write down some code for a FutureBuilder.
FutureBuilder(future: _myFuture, builder: (context, snapshot) {
if(snapshot.hasData) {
// !!!! IMPORTANT !!!
// Pay attention to the _isFirstText variable below
return SizedBox(
child: _isFirstText ? Text(snapshot.data.firstText) : Text(snapshot.data.secondText),
);
}
if(snapshot.connectionState == ConnectionState.isWaiting) {
return Text('Waiting!');
}
return Text('Error');
}),
As I have mentioned in a comment in the above code, pay attention to the _isFirstText variable. Suppose that is a state variable. Inside the future builder, how do I get the correct return value that corresponds to the isFirstText state variable change.
I also came across this stack overflow post but could not get the code to work.
I also came across a widget called StatefulBuilder, but I can not figure out to where in my FutureBuilder I should use it.
Can someone please help?
If you want to listen to ongoing changes you can use a Streambuilder. Streams are not only for serverside changes but can also be used locally.
You can build a Stream yourself like this :
StreamController myStreamController = StreamController<int>();
To send a new event through this controller you can do
myStreamController.sink.add(newValue);
You can then listen to changes like this:
#override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: myStreamController.stream,
builder: (context, snapshot) {
final value = snapshot.data;
return Text(value!.toString());
}
If you want to learn more check this Video: https://youtu.be/nQBpOIHE4eE
Let me know if this helped.
You can use ValueNotifier variables and use notifyListeners() to update a specific parts of your code Like this:
ValueListenableBuilder and use that ValueNotifier variable and listen to that.
I have a HomePage with DataTable with several hundred rows, which takes a while to load. The HomePageState reloads every time I sort the data table - while building, the whole page freezes for a second or two.
Is it possible to add a progress indicator, build the new state in the background and show it after the build is done?
In other words, I want this to happen when a user sorts the table:
CircularProgressIndicator shows up
The new state is shown after the new state is built
Hoping you are using FutureBuilder for getting data.
FutureBuilder(
future: fetchData(),
builder: (_, dataSnapshot) {
if (dataSnapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else {
return Container(), // Your widgets code
);
}
},
)
inside else block get data like dataSnapshot.data -- Use this to render widgets.
So the first picture has codes from the first page where I have a StreamBuilder in my flutter app, there I tried navigating to the stateful widget named PickupScreen containing the code in second picture and it gave me an error which says SetState( or markNeedsbuild() called during build.So I called the widget directly and it worked but the problem is I cannot go the previous page I know I can't use pop but I need a solution for returning to previous page and show the else code.Currently I am Navigating to another page named HomePage.
You can't do it like that. Either you manage your screen state based on your widget.channel.stream or you must design your UI in a different way so you can actually push a new screen on your screen 1 and then you will be able to pop.
If you still want to keep it like that, you can do something like the following in your first screen:
return StreamBuilder(
stream: widget.channel.stream,
builder: (context, snapshot){
if(snapshot.hasData && jsonDecode(snapshot.data['ntype'] == 10){
WidgetsBinding.instance.addPostFrameCallback((_) => Navigator.push(context, MaterialPageRoute(builder: (_) => PickupScreen(data: jsonDecode(snapshot.data))));
return const SizedBox();
} else {
return _isLoading ? LoadingScreen() : Scaffold();
}
});
Also, next time, make sure you copy/paste code instead of screenshots to make it easy to edit with modifications. :)