Why Flutter Streambuilder should be used over Futurebuilder
David S. N.
Cursor ai|C#|Web API|Python|Powershell|SQL|Flutter|OpenAI|LangChain|AI Agents|Dart|Chroma|Pinecone
Overview: Streambuilder should be used in most cases over futurebuilder because Streambuilder give you control when to update the stream. Widgets that use the Streambuilder query the stream upon different events like window resize requiring no journey back to a server because the stream is connected to a data island such as a list of objects. The list of objects are built by an api call to a server end point.
In this code snippet I demonstrate how to use a bottom sheet to load data into a list of MyClass types then in a business logic component (bloc) class. The bloc class contains the stream controller and data island list of myClass.
I call a future method called getObjectsOfMyClass which uses the inheritedWidget ApiWidget to retrieve a list of objects of myClass type. The restful get returns a json string which is serialized into a list of myClass objects using the classes factory mapping or serializing the json one object at a time. The factory uses the myClass constructor to serialize the json into the matching fields and field types. if the type is incorrect you will get a casting error.
Future<List<MyClass>>getObjectsOfMyClass(int userId) async { try { String url=_BASE_URL+'/api/checklist/GetObjectsOfMyClass/'+userId.toString(); var client= http.Client(); Map<String,String> headers= new HashMap(); headers['Accept']='application/json'; headers['Content-Type']= 'application/json'; var response = await client.get( url, headers:headers ).timeout(_TIMEOUT); if (response.statusCode==200) { var parsed=json.decode(response.body).cast<Map<String,dynamic>>(); var list = parsed.map<MyClass>((json)=>MyClass.fromJson(json)).toList(); return(list); } } catch(e){ throw Exception(e.toString());} return(null); }
The Stateful widget wrapping the Bottom sheet has a final myBloc variable. This variable is instantiated with the bloc class containing the stream and data island.
class MyWidget extends StatefulWidget { final MyBloc bloc=new MyBloc(); MyWidget({Key key}) : super(key: key); @override MyWidgetState createState() => MyWidgetState(); } class MyWidgetState extends State <MyWidget> {.......}
The bloc code contains a stream handler that watches for the last change on the stream before broadcasting to the subject of concern which will be the streambuilder. The streambuilder follows the observable pattern where subjects subscribe to a behavior. The behavior is observed then broadcast to the subjects which then perform a task such as rebuilding the datatable.
import 'package:rxdart/rxdart.dart'; class MyBloc{ List<MyClass> listMyClass; Stream <List<MyClass>> get myClassListStream => _myClassSubject.stream; final _scoreCardSummaryListSubject= BehaviorSubject<List<ScoreCardSummary>>(); initializeTheStream() { _myClassListSubject.add(listMyClass); } dispose(){ _myClassListSubject.close(); } }
The bottom sheet builder is connected to the blocMyClass stream and awaits a signal. This reminds me of the programming in unix using interprocessing communication where one process sends a message to another process. Flutter does not use processes and every task runs on a single thread.
showModalBottomSheet( context: context, builder: (BuildContext bc){ return StreamBuilder<List<MyClass>>( stream: widget.blocMyClass.MyClassListStream, builder:(context, snapshot) {...}
The streambuilder receives a list of myClass
class MyClass { String field1; MyClass( this.field1, ); factory MyClass.fromJson(dynamic json){ return MyClass( json['field1'] as String, ); } }
The Datatable is the end result. A data table consists of DataColumns and DataRows with DataCells that match to each DataColumn. I use the streambuilder data island to create a list of datarows calls rows. Lastly, I assign the rows to the DataTable for rendering. You can use streambuilder to load any widget type.
List<DataRow> rows = snapshot.data.map((MyClass MyClass){ return DataRow(cells:[ DataCell(Text(MyClass.field1??"")), ]); }).toList(); DataTable(columns: [ DataColumn(label: Text("Field 1")), ], rows:rows, ))
The final code
_settingModalBottomSheetMyClass(context){ int userId=ApiWidget.of(context).getUserId(); try { ApiWidget.of(context).getObjectsOfMyClass(userId).then((value) { widget.blocMyClass.listMyClass = value; widget.blocMyClass.initializeTheStream(); }).catchError((error) { DialogCaller.showErrorDialog(context, error); }); } catch (e) { DialogCaller.showErrorDialog(context, e.toString()); } showModalBottomSheet( context: context, builder: (BuildContext bc){ return StreamBuilder<List<MyClass>>( stream: widget.blocMyClass.MyClassListStream, builder:(context, snapshot) { //debugPrint(snapshot.connectionState.toString()); if (!snapshot.hasData) { return PleaseWaitWidget(); } else if(snapshot.hasError) { DialogCaller.showErrorDialog(context,"Stream builder in MyClass had an error").then((value){}); } else if (snapshot.hasData) { //return Text("workd"); List<DataRow> rows = snapshot.data.map((MyClass MyClass){ return DataRow(cells:[ DataCell(Text(MyClass.field1??"")), ]); }).toList(); return SingleChildScrollView( scrollDirection: Axis.horizontal, child:Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.end, children: [FlatButton( onPressed: (){Navigator.pop(context,true);}, child:Text("X",style: TextStyle(fontSize: 10),))],), Expanded(child: DataTable(columns: [ DataColumn(label: Text("Field 1")), ], rows:rows, )) ])); } return(Container(child:Text("Failed"))); }); }); } In short, I have shown how to use a streambuilder and bloc class to map to a datatable using an observable pattern. Flutter programming is much better than knock out or angular or react because it is a programmable interface. I think flutter will become much more popular over time as companies migrate their code into a single code base for all devices.