How to reload the page whenever the page is on screen - flutter

Viewed 23552

Is there any callbacks available in flutter for every time the page is visible on screen? in ios there are some delegate methods like viewWillAppear, viewDidAppear, viewDidload.

I would like to call a API call whenever the particular page is on-screen.

Note: I am not asking the app states like foreground, backround, pause, resume.

Thank You!

5 Answers

Specifically to your question:

Use initState but note that you cannot use async call in initState because it calls before initializing the widget as the name means. If you want to do something after UI is created didChangeDependencies is great. But never use build() without using FutureBuilder or StreamBuilder

Simple example to demostrate:

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(MaterialApp(home: ExampleScreen()));
}

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

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

class _ExampleScreenState extends State<ExampleScreen> {
  List data = [];
  bool isLoading = true;

  void fetchData() async {
    final res = await http.get("https://jsonplaceholder.typicode.com/users");
    data = json.decode(res.body);
    setState(() => isLoading = false);
  }

  // this method invokes only when new route push to navigator
  @override
  void initState() {
    super.initState();
    fetchData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: isLoading
            ? CircularProgressIndicator()
            : Text(data?.toString() ?? ""),
      ),
    );
  }
}

Some lifecycle method of StatefulWidget's State class:

initState():

Describes the part of the user interface represented by this widget.

The framework calls this method in a number of different situations:

After calling initState.
After calling didUpdateWidget.
After receiving a call to setState.
After a dependency of this State object changes (e.g., an InheritedWidget referenced by the previous build changes).
After calling deactivate and then reinserting the State object into the tree at another location.

The framework replaces the subtree below this widget with the widget returned by this method, either by updating the existing subtree or by removing the subtree and inflating a new subtree, depending on whether the widget returned by this method can update the root of the existing subtree, as determined by calling Widget.canUpdate. Read more

didChangeDependencies():

Called when a dependency of this State object changes.

For example, if the previous call to build referenced an InheritedWidget that later changed, the framework would call this method to notify this object about the change.

This method is also called immediately after initState. It is safe to call BuildContext.dependOnInheritedWidgetOfExactType from this method. Read more

build() (Stateless Widget)

Describes the part of the user interface represented by this widget.

The framework calls this method when this widget is inserted into the tree in a given BuildContext and when the dependencies of this widget change (e.g., an InheritedWidget referenced by this widget changes). Read more

didUpdateWidget(Widget oldWidget):

Called whenever the widget configuration changes.

If the parent widget rebuilds and request that this location in the tree update to display a new widget with the same runtimeType and Widget.key, the framework will update the widget property of this State object to refer to the new widget and then call this method with the previous widget as an argument. Read more

Some widgets are stateless and some are stateful. If it's a stateless widget, then only values can change but UI changes won't render.

Same way for the stateful widget, it will change for both as value as well as UI.

Now, will look into methods.

  1. initState(): This is the first method called when the widget is created but after constructor call.

@override
void initState() {
   // TODO: implement initState
   super.initState();
}
  1. didChangeDependecies() - Called when a dependency of this State object changes.Gets called immediately after initState method.

@override
  void didChangeDependencies() {
    super.didChangeDependencies();
  }
  1. didUpdateWidget() - It gets called whenever widget configurations gets changed. Framework always calls build after didUpdateWidget

@override
void didUpdateWidget (
   covariant Scaffold oldWidget
)
  1. setState() - Whenever internal state of State object wants to change, need to call it inside setState method.

 setState(() {});
  1. dispose() - Called when this object is removed from the tree permanently.

@override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
  }

You don't need StatefulWidget for calling the api everytime the screen is shown.

In the following example code, press the floating action button to navigate to api calling screen, go back using back arrow, press the floating action button again to navigate to api page.

Everytime you visit this page api will be called automatically.

import 'dart:async';

import 'package:flutter/material.dart';

main() => runApp(MaterialApp(home: HomePage()));

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      floatingActionButton: FloatingActionButton(
        onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => ApiCaller())),
      ),
    );
  }
}

class ApiCaller extends StatelessWidget {
  static int counter = 0;

  Future<String> apiCallLogic() async {
    print("Api Called ${++counter} time(s)");
    await Future.delayed(Duration(seconds: 2));
    return Future.value("Hello World");
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Api Call Count: $counter'),
      ),
      body: FutureBuilder(
        future: apiCallLogic(),
        builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) return const CircularProgressIndicator();

          if (snapshot.hasData)
            return Text('${snapshot.data}');
          else
            return const Text('Some error happened');
        },
      ),
    );
  }
}

This is the simple code with zero boiler-plate.

The simplest way is to use need_resume

1.Add this to your package's pubspec.yaml file:

dependencies:
  need_resume: ^1.0.4

2.create your state class for the stateful widget using type ResumableState instead of State

class HomeScreen extends StatefulWidget {
    @override
    HomeScreenState createState() => HomeScreenState();
}

class HomeScreenState extends ResumableState<HomeScreen> {
    @override
    void onReady() {
        // Implement your code inside here

        print('HomeScreen is ready!');
    }

    @override
    void onResume() {
        // Implement your code inside here

        print('HomeScreen is resumed!');
    }

    @override
    void onPause() {
        // Implement your code inside here

        print('HomeScreen is paused!');
    }


    @override
    Widget build(BuildContext context) {
        return Scaffold(
            body: Center(
                child: RaisedButton(
                    child: Text('Go to Another Screen'),
                    onPressed: () {
                       print("hi");
                    },
                ),
            ),
        );
    }
}

If you want to make an API call, then you must be (or really should be) using a StatefulWidget.

Walk through it, let's say your stateful widget receives some id that it needs to make an API call.

Every time your widget receives a new id (including the first time) then you need to make a new API call with that id.

So use didUpdateWidget to check to see if the id changed and, if it did (like it does when the widget appears because the old id will be null) then make a new API call (set the appropriate loading and error states, too!)

class MyWidget extends StatefulWidget {
  Suggestions({Key key, this.someId}) : super(key: key);

  String someId

  @override
  State<StatefulWidget> createState() => MyWidgetState();

}

class MyWidgetState extends State<MyWidget> {

  dynamic data;
  Error err;
  bool loading;

  @override
  Widget build(BuildContext context) {
     if(loading) return Loader();
     if(err) return SomeErrorMessage(err);
     return SomeOtherStateLessWidget(data);
  }


  @override
  void didUpdateWidget(covariant MyWidget oldWidget) { 
    super.didUpdateWidget(oldWidget);   

    // id changed in the widget, I need to make a new API call
    if(oldWidget.id != widget.id) update();
  }

  update() async {
    // set loading and reset error
    setState(() => {
      loading = true,
      err = null
    });

    try {
        // make the call
        someData = await apiCall(widget.id);
        // set the state
        setState(() => data = someData)
    } catch(e) {
        // oops an error happened
        setState(() => err = e)
    }

    // now we're not loading anymore
    setState(() => loading = false);
  }

}

I'm brand new to Flutter (literally, just started playing with it this weekend), but it essentially duplicates React paradigms, if that helps you at all.

Personal preference, I vastly prefer this method rather than use FutureBuilder (right now, like I said, I'm brand new). The logic is just easier to reason about (for me).

Related