Is nested StreamBuilder good pattern? - flutter

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.

Related

Flutter 2 FutureBuilder inside 1 BuildContext

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!

what is the best practice to request to Firebase Firestore in every tabBarView ? Flutter

I am trying to request in every tab to Firebase Firestore. Still, if I do not add a listener to the tabController with setState((){}) it does not refresh the view and the setting state adds to the screen's momentary rerendering effect. I want to avoid this effect. Thanks for all answers.
Firestore have an awesome snapshot build right into it. you can combine that with StreamBuilder from Flutter.Streambuilder will listen to the changes from Firestore using snapshot. you don't need to worry about the state (you can use stateless) as Streambuilder with auto rebuild every time the data on your Firestore update.
return StreamBuilder<QuerySnapshot<Map<String, dynamic>>>(
stream: db.collection('drugs').snapshots(),
builder: ((context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
} else if (!snapshot.hasData) {
return const Center(
child: Text('No Drug'),
);
} else {
final list = snapshot.data!.docs
.map((e) => Drug.fromMap(e.data()))
.toList();
return ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
return ProductTile(list[index]);
},
);
}
})),

Continuously Retrieving data from stream builder

I am trying to implement stream builder with cloud firestore to retrieve field data.
Here is the code:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("test builder"),
),
body: StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection('joystick').snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot){
if(!snapshot.hasData){
return Center(
child: CircularProgressIndicator(),
);
}
return ListView.builder(
itemCount: snapshot.data?.docs.length,
itemBuilder: (context, i){
QueryDocumentSnapshot<Object?>? ds = snapshot.data?.docs[i];
return Text("$snapshot.data?.docs[i].data()!['call']");
});
}
),
);
}
However, it does not output the actual data stored in the database.
I get the following output:
AsyncSnapshot<QuerySnapshot<Object?
>>(ConnectionState.active,Instance of '_JsonQuerySnapshot',null, null).data?.docs[i].data()!['call']
What should I do to get the data stored in the database? (The field name is 'call')
In order to implement stream builder with cloud firestore to retrieve field data, you can follow the StreamBuilder Documentation and link related to explanation of streambuilder in flutter.
As mentioned the thread, you can go through the following code on retrieving data through streambuilder :
body: new StreamBuilder(
stream: Firestore.instance.collection("collection").snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text( 'No Data...', );
}
else {
<DocumentSnapshot> items = snapshot.data.documents;
return new Lost_Card(
headImageAssetPath : items[0]["url"] );
}
If you want to create list builder from many documents use it like
this
return new ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot ds = snapshot.data.documents[index];
return new Lost_Card(
headImageAssetPath : ds["url"];
);
For more information, you can refer to the Documentation where real time updates with cloud firestore have been explained.
Try this one to return your data
return Text(ds['enterYourDataFieldName']);
What you are trying to achieve concerns RealTime database, Firestore database is not made for that.

The body might complete normally, causing 'null' to be returned, but the return type is a potentially non-nullable type. / Flutter

I just started writing with Flutter.I am constantly getting this error. What should i do? Here is my code:
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<WeatherInfo>(
future: futureWeather,
builder: (context, snapshot) {
if (snapshot.hasData) {
} else if (snapshot.hasError) {
return Center(
child: Text("${snapshot.error}"),
);
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<WeatherInfo>(
future: futureWeather,
builder: (context, snapshot) {
if (snapshot.hasData) {
} else if (snapshot.hasError) {
return Center(
child: Text("${snapshot.error}"),
);
}
return const Center(child: CircularProgressIndicator());
}));
}
Inside the FutureBuilder you covered the case in which you have the data or you have an error, but not when you are expecting for the future to complete(And don't have neither the data or an error).
I just added a Circular progress indicator to be shown while no data or no error are returned from the Future, that should prevent the FutureBuilder from returning null. And when the snapshot state changes the data or error would be shown.
I think you should try and wrap the future builder in a container

display a markdown file in flutter app and use the reference links

I am trying to take a markdown file, contents.md, and then display that on a page in my app, but I want to be able to use the reference links that I have added that point to different files, chapter1.md, chapter2.md, chapter3.md, and so on. I have been able to display markdown formatted from contents.md, but the links don't work.
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("Flutter Markdown"),),
body: FutureBuilder(
future: rootBundle.loadString("assets/manual/contents.md"),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Markdown(data: snapshot.data);
}
return Center(
child: CircularProgressIndicator(),
);
}),
);
}
Is there a way to do it? Because Google hasn't helped at all and I am seriously doubting that it is possible.
I had to create a variable outside of the class, and then make a getter and setter for it, then make the future builder a method and call setState(){} after setting the variable.
String file = "contents.md";
class _ManualState extends State<Manual> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(getFile()),),
body: displayMarkdown(getFile())
);
}
FutureBuilder<String> displayMarkdown(String file){
return FutureBuilder(
future: DefaultAssetBundle.of(context).loadString
("assets/manual/" + file),
builder: (BuildContext context, AsyncSnapshot<String> snapshot){
if (snapshot.hasData) {
return Markdown(data: snapshot.data, onTapLink: (link){
setFile(link);
setState((){});
},
);
}
return Center(
child: CircularProgressIndicator(),
);
},
);
}
}
String getFile() {
return file;
}
String setFile(String name) {
file = name;
return file;
}
Flutter has great support for additional libraries. There is a library for markdown files as well, you can find it here.