Why Flutter Streambuilder should be used over Futurebuilder

Why Flutter Streambuilder should be used over Futurebuilder

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.


要查看或添加评论,请登录

David S. N.的更多文章

社区洞察

其他会员也浏览了