How to detect an asynchronous operation is over in

2019-08-18 08:47发布

I am using StreamBuilder like this -

StreamController streamController = StreamController.broadcast();

StreamBuilder(
    stream: streamController.stream,
    builder: (BuildContext context, AsyncSnapshot snapshot) {
      return (snapshot.hasData == true)  //THIS CONDITION!!!
          ? CircularProgressIndicator()
          : myWidget();
)

I am adding to my stream like this -

onPressed: () {
     streamController.add(null);
 },

I am unable to understand the condition I should check to display the progress indicator or my widget. Since I am not passing any data, I can't use hasData. I tried passing dummy data but hasData never becomes false (even after the async function is over). I tried using connectionState but that is always active. I have no idea why it is not changing to waiting state. It does when I use FutureBuilder. (I thought I could just check that if the state is waiting, show the progress indicator but that doesn't work. Why??)

Please help.

1条回答
We Are One
2楼-- · 2019-08-18 09:27

Always when I have an async call that returns me Future data and also I need this response to update my UI layer using streams I think in BLoC pattern. Is needed a little bit more code but will simplify your problem and make your code more readable, scaleable, and keep streams and async calls out from UI code as other things too.

In your case It's simple:

enum DownloadState { NO_DOWNLOAD,  DOWNLOADING, SUCCESS }

An enum to track what is the state of the async call a Network API call by example.

class Bloc {

  final ApiClass api = new ApiClass();   // simulating your downloader object
  final StreamController controller = StreamController<DownloadState>.broadcast(); 
  Stream get dataState => controller.stream; // exposing your stream output

 void _changeState( final DownloadState state ) => controller.sink.add( state );

  void downloadData(){
      _changeState( DowloadState.DOWNLOADING );     
      // assuming that this call returns a Future object.
      api.downloadData().then(  (yourNetworkData) {
         // handle your downloaded data 
        _changeState( DownloadState.SUCCESS );
       }  ).catchError( (apiError) =>  controller.sink.addError( apiError ); ); 
 }
}

A BLoC class that expose a stream and inside this class we create method that call a Network API by example and when the results is fetched we can hold the values, transform this values, send to UI using streams and other things. Here in downloadData after the Future object is completed we just put into stream a DownloadState value and this will force the widgets that are listening controller.stream be rebuilded in UI layer with StreamBuilder help. If we got some error from network call we put this in stream error output controller.sink.addError and in UI layer we'll able to check this with snapshot.hasError property.

In your UI layer...

Bloc bloc = Bloc(); // you can create a instance of BLoC in initState or in widget contructor...
StreamBuilder<DownloadState>(
    stream: bloc.dataState, // bloc get method that returns stream output.
    initialData: DownloadState.NO_DOWNLOAD. // this will be the inital value. It's optional
    builder: (BuildContext context, AsyncSnapshot snapshot) {
      if (snapshot.hasData){
          switch(snapshot.data){
               case  DownloadState.NO_DOWNLOAD:
                  return NoDownloadWidget();
               case  DownloadState.DOWNLOADING:
                  return CircularProgressIndicator();
                case  DownloadState.SUCCESS:
                   return myWidget();
          }
      }
       return ErrorWidget( snapshot.error );
)

and in the onPress Event

onPressed: () => bloc.downloadData();

With this approach you remove of your UI layer stream objects and controllers, no setState calls is needed, you will have a specific file with your business logic and external api calls and this can make more easy this kind of job where we have Futures and Streams working together.

I Hope it helps.

查看更多
登录 后发表回答