I think this situation is a little odd. I'm using two FutureBuilders inside one BuildContext. However, the second FutureBuilder is not working, which means the connectionState is always waiting, and I literally have no idea what's going on.
First FutureBuilder inside AppBar()
final appBar = AppBar(title: FutureBuilder(
future: futureThatWillReturnAString(),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Searching location...");
}
return Text(snapshot.data);
},
));
Second FutureBuilder inside Widget build(BuildContext) {}
appBar: appBar,
body: FutureBuilder(
future: futureThatWillReturnAMap(),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading lists");
}
return ListView.builder(...);
},
)
Only the AppBar will finish the future, while the second FutureBuilder remains in the loading state. Please tell me if I'm missing something or if there is something wrong in my implementation. Thanks!
Related
I have a little problem here where i have logged in with Google Auth using Firebase but everytime i tried to restart the app i expect the app will show the HomePage() without any problem, but i found that before it return, the app had like a bit seconds in LoginPage() before displaying HomePage(), is there any way to make it seamlessly
class AuthService extends StatelessWidget {
const AuthService({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return HomePage();
} else {
return LoginPage();
}
},
),
);
}
}
It is happening because for snapshot to reach snapshot.hasData state it takes time, and meanwhile else part is executed which is LoginPage().
How to overcome this?
Try to wrap within snapshot.connectionState == ConnectionState.active which means once stream is connected then check the condition else return CircularProgressIndicator
Code:
StreamBuilder(
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.hasData) {
return HomePage();
} else {
return LoginPage();
}
}
return const CircularProgressIndicator();
},
);
I have looked all over and cannot seem to find a solution to this problem. I have a Future<bool> = isAuth.
I have a future builder defined as follows:
FutureBuilder(
future: isAuth,
builder(context, snapshot){
if(connectionState == connectionState.done){
// Some Code Here
}
if(connectionState == ConnectionState.waiting){
CircularProgressIndicator()
}
}
My question is, how do I return one widget if isAuth returns true, and another if isAuth returns false? I am displaying a loading spinner while waiting on the result of isAuth, and once I have the result, I want to display one of two forms?
You can access a FutureBuilder's future value with snapshot.data.
Don't forget to check if snapshot.data is defined using snapshot.hasData :
FutureBuilder<bool>(
future: isAuth,
builder: (context, snapshot) {
if(connectionState == connectionState.done){
if (snapshot.hasData){
if (snapshot.data) return TrueWidget();
else return FalseWidget();
// Or with a one-liner :
// return snapshot.data ? TrueWidget() : FalseWidget();
}
}
if(connectionState == ConnectionState.waiting){
return CircularProgressIndicator(); // Don't forget to return a widget in a builder method
}
}
)
new FutureBuilder<bool>(
future: isAuth, // async work getting the call for true of false
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
bool value = snapshot.data;
switch (snapshot.connectionState) {
case ConnectionState.waiting: return new Text('Loading....');
default:
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else
return value ?
new Text('sample one'): Text('sample two')
}
},
)
I am fetching articles from HackerNews API using Bloc Pattern and Streams.
I am loading all the articles and presenting in the UI with the help of a stream builder, and this works fine.
Now I wrapped the article fetching Stream builder with the new loading StreamBuilder.
Now when the loading stream builder has true (means it is loading) it shows a circular progress indicator or else, it shows the child (Article List wrapped with a Stream Builder).
This works fine. But it is bugging me that I have wrapped Stream builder inside a stream builder. I know I can take help of rxdart but I am just not sure how.
I tried to add a loader with the help of snapshot.hasData or not but that didn't work, so I decided to create another stream and subject that takes a bool and tells the UI if it is loading or not.
Code fetching data int the bloc:
_getAndUpdateArticles(StoryType storyType) {
_isLoadingSubject.add(true);
getListIds(storyType).then((list) {
getArticles(list.sublist(0, 10)).then((_){
_articleSubject.add(UnmodifiableListView(_articles));
_isLoadingSubject.add(false);
});
});
}
UI:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: StreamBuilder(
stream: widget.hnBloc.isLoading,
builder: (context, snapshot) {
if (snapshot.data) {
return Center(child: CircularProgressIndicator());
} else {
return StreamBuilder<UnmodifiableListView<Article>> (
initialData: UnmodifiableListView<Article>([]),
stream: widget.hnBloc.article,
builder: (context, snapshot) => ListView(
children: snapshot.data.map(_buildItem).toList(),
),
);
}
},
),
.........
EDIT
I have tried this, but this isn't working:
StreamBuilder<UnmodifiableListView<Article>> (
initialData: UnmodifiableListView<Article>([]),
stream: widget.hnBloc.article,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView(
children: snapshot.data.map(_buildItem).toList(),
);
} else {
return CircularProgressIndicator();
}
}
),
I Don't think there is a complete way to avoid nested StreamBuilders. I personally wouldn't consider it a bad practice, but it will definitely lead to more build.
In your case, You can modify your hnBloc to emit a single state that can be a loading state or data state , thereby eliminating the need for a nested StreamBuider.
eg.
StreamBuilder<HnState>(
stream: hnBloc.currentState,
initialData: HnLoadingState(),
builder: (context, snapshot) {
if (snapshot.data is HnLoadingState) {
return Center(child: CircularProgressIndicator());
}if (snapshot.data is HnDataState) {
return ListView(
children: snapshot.data.map(_buildItem).toList(),
),
}
},
)
This pattern is very common when using the flutter_bloc package. You can see a basic example of this here to understand it better.
Is there any case possible using StreamBuilder for other widgets other than ListView and GridView...?
Let's say ChoiceChip? Why ChoiceChip doesn't have builder?
Yes. we can use any other widget with Stream
see official docs
StreamBuilder(
stream: bloc.allMovies,
builder: (context, AsyncSnapshot<ItemModel> snapshot) {
if (snapshot.hasData) {
return Text("data incoming");
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Center(child: CircularProgressIndicator());
},
)
I'm populating a ListView from Streambuilder and want to show the length/nr of documents in the AppBar title. Right now I'm calling SetState everytime there's a change in the stream. It works but "feels" kinda resource heavy. Any ideas?
Thanks in advance.
Best,
/j
StreamBuilder(
stream: Firestore.instance.collection('users').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text('Loading...');
appBarTitle = snapshot.data.documents.length;
Future.delayed(Duration.zero, () {
setState(() {
});
});
},
);
you can wrap title of appBar with Stream builder to update your screen title like this code
AppBar(title: StreamBuilder<Object>(
stream: bloc.myStream,
builder: (context, snapshot) {
return yourCustomWidget();
}
)