4

I am using Flutter 1.2.1 in the Stable branch. To illustrate my problem imagine I have pages A and B. A navigates to B using Navigator.push and B navigates back to A using Navigator.pop. Both are stateful widgets.

When I navigate from A to B and then pop back to A everything is fine and A keeps its state. However, if I navigate from A to B, tap a textfield in B opening the keyboard, then close the keyboard and pop back to A, A's entire state is refreshed and the initState() method for A is called again. I verified this by using print statements.

This only happens when I open the keyboard before popping back to A. If I navigate to B, then immediately navigate back to A without interacting with anything then A keeps its state and is not re-initialized.

From my understanding the build method is called all the time but initState() should not get called like this. Does anyone know what is going on?

Graham
  • 5,488
  • 13
  • 57
  • 92

5 Answers5

4

After much trial and error I determined the problem. I forgot that I had setup a FutureBuilder for the / route in my MaterialApp widget. I was passing a function call that returns a future to the future parameter of the FutureBuilder constructor rather than a variable pointing to a future.

So every time the routes got updated a brand new future was being created. Doing the function call outside of the MaterialApp constructor and storing the resulting future in a variable, then passing that to the FutureBuilder did the trick.

It doesn't seem like this would be connected to the weird behavior I was getting when a keyboard opened, but it was definitely the cause. See below for what I mean.

Code with a bug:

return MaterialApp(
  title: appTitle,
  theme: ThemeData(
    primarySwatch: Colors.teal,
    accentColor: Colors.tealAccent,
    buttonColor: Colors.lightBlue,
  ),
  routes: {
    '/': (context) => FutureBuilder<void>(
          future: futureFun(), //Bug! I'm passing a function that returns a future when called. So a new future is returned each time
          builder: (context, snapshot) {
          ...
          }
      ...
  }
  ...
}

Fixed Code:

final futureVar = futureFun(); //calling the function here instead and storing its future in a variable

return MaterialApp(
  title: appTitle,
  theme: ThemeData(
    primarySwatch: Colors.teal,
    accentColor: Colors.tealAccent,
    buttonColor: Colors.lightBlue,
  ),
  routes: {
    '/': (context) => FutureBuilder<void>(
          future: futureVar, //Fixed! Passing the reference to the future rather than the function call
          builder: (context, snapshot) {
          ...
          }
      ...
  }
  ...
}
Graham
  • 5,488
  • 13
  • 57
  • 92
0

did you use AutomaticKeepAliveClientMixin in "A" widget ? if you don't , see this https://stackoverflow.com/a/51738269/3542938 if you already use it , please give us a code that we can test it directly into "main.dart" to help you

abdalmonem
  • 1,367
  • 1
  • 10
  • 19
  • Thanks for the answer. I wasn't able to try this out until this morning. It didn't work. There seems to be something happening when the keyboard opens which causes everything to get recreated. This didn't happen prior to version 1.2. – Graham Mar 20 '19 at 15:08
  • maybe a bug , try to report flutter team , by open issue at : [https://github.com/flutter/flutter/issues] – abdalmonem Mar 20 '19 at 15:38
0

Yup, happened to me, perhaps it's much better to wrap the FutureBuilder itu a PageWidget, and make it singleton

    return MaterialApp(
      title: appTitle,
      theme: ThemeData(
        primarySwatch: Colors.teal,
        accentColor: Colors.tealAccent,
        buttonColor: Colors.lightBlue,
      ),
      routes: {
        '/': (context) => PageWidget() // wrap it by PageWidget
          ...
      }
      ...
    }


    class PageWidget extends StatelessWidget {
      static final _instance = PageWidget._internal(); // hold instance

      PageWidget._internal(); // internal consturctor

      factory PageWidget() {
        return  _instance;    // make it singleton
      }

      @override
      Widget build(BuildContext context) {
        return FutureBuilder<void>( ... );
      }
    }
aristo_sh
  • 4,848
  • 2
  • 13
  • 13
0

I got a solution, I was initialising variables in the constructor of the superclass. I removed it and worked!

Pratik Pitale
  • 1,655
  • 1
  • 16
  • 30
0

I just removed the FutureBuilder from the home of MaterialApp and changed the MyApp into a Stateful widget and fetched the requisite info in the initState and called setState in the .then(); of the future and instead of passing multiple conditions in the home of MaterialApp, I moved those conditions to a separate Stateful widget and the issue got resolved.

initState:

 @override
  void initState() {
    // TODO: implement initState
    // isSignedIn = SharedPrefHelper.getIsSignedIn();

    getIsSignedInFromSharedPreference().then((value) {
      setState(() {
        isSignedInFromSharedPref = value ?? false;
        if (isSignedInFromSharedPref) {
          merchantKey = LocalDatabase.getMerchantKeyWithoutAsync();
        }
        isLoadingSharedPrefValue = false;
      });
    });
    super.initState();
  }

  Future<bool?> getIsSignedInFromSharedPreference() async {
    return SharedPrefHelper.getIsSignedIn();
  }

MaterialApp (now):

MaterialApp(
        title: 'Loveeatry POS',
        debugShowCheckedModeBanner: false,
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Home(
          isLoadingSharedPrefValue: isLoadingSharedPrefValue,
          isSignedInFromSharedPref: isSignedInFromSharedPref,
          merchantKey: merchantKey,
        ),
      ),

Home:

class Home extends StatelessWidget {
  final bool isLoadingSharedPrefValue;
  final bool isSignedInFromSharedPref;
  final String merchantKey;
  const Home({
    Key? key,
    required this.isLoadingSharedPrefValue,
    required this.isSignedInFromSharedPref,
    required this.merchantKey,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    if (!isLoadingSharedPrefValue) {
      if (isSignedInFromSharedPref) {
        return const Homepage(
          shouldLoadEverything: true,
        );
      } else if (merchantKey.isNotEmpty) {
        return LoginPage(merchantKey: merchantKey);
      } else {
        return const AddMerchantKeyPage();
      }
    } else {
      return loading(context);
    }
  }
}

P.S.: If you need any more info, please leave a comment.

Waqad Arshad
  • 329
  • 1
  • 9