简体   繁体   中英

Flutter: How to Notify main widget after inserting all data into database

In my StatefulWidget in initState i have a method:

  @override
  void initState() {
    super.initState();
    getMyChannels();
  } 

getMyChannels method run a http method to get data from service and store data into database:

  void getMyChannels() async {
    // get data from servise and store them
    _myChannel = await MyToolsProvider()
        .getChannelMe("df6b88b6-f47d****");
    getToolsRelToChannels(); // get data from database
    setState(() {});
  }

As you can see i have getToolsRelToChannels method. This method fetch data from local database. This data must be stored by await MyToolsProvider() .getChannelMe("df6b88b6-f47d****"); method into database.

This is .getChannelMe method:

  Future<ProgramsByActiveToolsModel> getChannelMe(String auth) async {
    Map<String, dynamic> header = {
      'Content-Type': "application/json",
      "Authorization": 'Bearer $auth'
    };
    try {
      var result = await NetworkCLient()
          .getRequest(url: '$URL/api/me', header: header);
      if (result != null) {
        var programsByActiveToolsModel =
            ProgramsByActiveToolsModel.fromJson(result);
        if (programsByActiveToolsModel.responseCode == 200) {
          programsByActiveToolsModel.data.forEach((item) async {
            await DBProvider.db.addMyTools(item);
            saveToolsbyChannelId(header, item.id);
          });
          return programsByActiveToolsModel;
        } else
          return null;
      }
    } catch (e) {
      throw e;
    }
  }

In addMyTools method i store each data in one table of my database and i call saveToolsbyChannelId method for each item. This is main data that I need too.

  Future<void> saveToolsbyChannelId(Map header, int channelId) async {
    header["Authorization"] = 'Bearer 92122926-****';

    try {
      var count = await DBProvider.db.getCountToolsbyChannelId(channelId);
      if (count == 0) {
        var result = await NetworkCLient().getRequest(
            url: '$URL/api/channel/$channelId', header: header);
        if (result != null) {
          var logToolsRunModel = LogTools.LogToolsRunModel.fromJson(result);
          if (logToolsRunModel.responseCode == 200) {
            logToolsRunModel.data.forEach((item) {
              DBProvider.db.addTools(item);
            });
          }
        }
      }
    } catch (e) {
      throw e;
    }
  }

After fetching data from my service i sore these data into sqlite database .Now it's await MyToolsProvider().getChannelMe job is done!

It's time to explain getToolsRelToChannels(); :

  void getToolsRelToChannels() async {

    _toolsRun =
        await MyToolsProvider().getToolsRelatedMyChannel(_selectedChannel);
    setState(() {});
  }

getToolsRelatedMyChannel this method must be wait till all data in this method DBProvider.db.addTools(item) added into database and after inserting my widget must be recreated.

  Future<List<ToolsByChannelIdDbModel>> getToolsRelatedMyChannel(
      int channelId) async {
    List<ToolsByChannelIdDbModel> list = List<ToolsByChannelIdDbModel>();

    try {
      var result = await DBProvider.db.getToolsById(channelId);
      result.forEach((item) {
        list.add(ToolsByChannelIdDbModel.fromJson(item));
      });
      return list;
    } catch (e) {
      print(e);
    }
  }
}

but my code is wrong because after running await MyToolsProvider().getChannelMe(***) getToolsRelToChannels method is executed and nothing is stored into database to fetching yet!!!

How could i notify my main widget after finishing database inserting??? I can not to use FutureBuilder because when run for first time, my database is empty !!!

You should await saveToolsbyChannelId in getChannelMe and await DBProvider.db.addTools(item); in saveToolsbyChannelId , otherwise you are trying to read from the database before the data has been written to it. This is assuming the rest of your code is correct, which we cannot tell for sure because there are lots of variables such as _selectedChannel that we know nothing about.

UPDATED - Check below. What you want is to await ALL async operations. In your case

@override
  void initState() async {
    super.initState();
    await getMyChannels();
  }

and

await saveToolsbyChannelId(header, item.id);

and if DBProvider.db.addTools is asynchronous, then

await DBProvider.db.addTools(item);

UPDATE:

Since its not possible to make initState() async , you can use a callback in the future:

@override
  void initState() {
    super.initState();
     var channelsFuture = getMyChannels();
     channelsFuture.then((resp){
         setState(() {});
     });
  }

I'd suggest that you reconsider your whole approach (from a clean architecture point of view). Take a look at the BLoC pattern . It basically boils down to this :

  • There's another class (called the BLoC) which handles the business logic (getting data from network and adding to the database). The widget class only handles the UI.
  • You kick off your asynchronous processing in the BLoC class from the widget's initState() . There's a stream listener in the widget that listens to the completion of the task by BLoC.
  • As soon as the BLoC completes the async task, it notifies the widget by using a stream. The stream listener in the widget knows that the data is ready and it updates the UI by calling setState();
  • Done !!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM