flutterdartasync-awaitfuture

Flutter: how to use function result in multiple classes without repeating the function


I have some pages that contain dependent content, which I retrieve from an API using multiple fetch requests.

Here, getClass() and getValue() are calling API from another class, returning future.

List<dynamic> pIds, ps;
apiStart() async {
  pageIds = new List();
  siteCls = await _baseApi.getClass(siteCName);
  siteCId = siteCls.data.classId.toString();

  pageCls = await _baseApi.getClass(pageCName);
  pageCId = pageCls.data.classId.toString();

  siteIns = await _baseApi.getValue(siteCId);
  pageIns = await _baseApi.getValue(pageCId);

  pageIds = getPageIds();
  pages = getPagesData();
}

List getPageIds() {
  //Doing some work here
  return pIds;
}

List getPagesData() {
  //Doing some work here
  return ps;
}

Now I want to access variables like "pages" from other/multiple classes without repeating the functions. How can I do that?

Update:

This is a sample code. It return "welcome test1!". When I remove content = "test1"; from apiStart(), returns "welcome h!". Is there an issue with using the await?

class AppData {
  factory AppData() {
    return _singleton;
  }
  static final AppData _singleton = AppData._internal();
  AppData._internal() {
    print("Instance created App-Data");
  }

  BaseApi _baseApi = new BaseApi();

  var content = "hi";
  Future body() {
    return Future.value(content);
  }

  void apiStart() async{
    content = "test1";//OK when I put content here 
    print(content);

    _ids = await _baseApi.fetchIds();
    //Does not change when I put content here
    //I need to use await because I need ids for next function
    content = "test2";  
    print(content);

    //Doing some work with result and get ids as list
    List id = idList();
     
    _pages = await _baseApi.fetchPages(id[0]);
    //Doing some work with result and get page content
    contents = contentList();
  }
}

If I do something like below, it returns the "test2". But I don't know how to check if the job has finished or not; Should I add a boolean var at the end of the function and check it every seconds? Is that efficient?

Future body() {
  return Future.delayed(Duration(seconds: 10)).then((value) => content);
}

Where I run singleton

void main() {
  runApp(MyApp());
  AppData().apiStart();
}

I show part of the results like this:

class ContentWidget extends StatefulWidget {
  ContentWidget({Key key}) : super(key: key);

  @override
  _ContentWidgetState createState() => _ContentWidgetState();
}

class _ContentWidgetState extends State<ContentWidget> {
  Future<dynamic> futureContent;

  @override
  void initState() {
    super.initState();
    
    futureContent = AppData().body();
  }

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<dynamic>(
      future: futureContent,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return Text("Welcome ${snapshot.data}!", style: plainTextStyle);
        } else {
          //return Text("Welcome ${snapshot.error}!", style: plainTextStyle);
        }
        // By default, show a loading spinner.
        return CircularProgressIndicator();
      },
    );
  }
}

Solution

  • You could use a Singleton pattern to create a service class: it gives you access to this class from anywhere in your code.

    Don't over use it, because it can turn into a very messy globals-like lack of encapsulation.

    Note: this is a huge simplification, but will work. Read aroudn singletons and services to find out more.

    class ApiService {
      // next 5 lines is a typical singleton pattern
      static ApiService _singleton;
      factory ApiService() {
        return _singleton ??= new ApiService._internal();
      }
      ApiService._internal();
    
      //here you'll keep your data
      List<dynamic> pageIds, pages; //store your data in these variables
    
      //run this on the beginning of your app to get your data. Don't forget this!
      void apiStart() async {
        // your code here where you hit the API and get all your data,
        // eg methods getPageIdsFromAPI and getPagesDataFromAPI that you need to write. They return your data
        //    pageIds = getPageIdsFromAPI();
        //    pages = getPagesDataFromAPI();
      }
    }
    

    then from ANYWHERE in your code you can access variables and functions from this class

    ApiService().pageIds
    ApiService().pageIds.length
    ApiService().pageIds.contains(some_id)
    

    ---EDIT---

    you need to decide when the waiting would happen. Either you wait for the data before you show the screen, or you show the screen in an empty/waiting mode (like facebook does) and then wait.

    to do the second one:

    @override
    void initState() { 
      WidgetsBinding.instance.addPostFrameCallback((_) { 
        futureContent = await AppData().body();
        setState(() { 
          // refresh whatever changed. it's a good practice to specify what 
        });
      });
    }