'Preserve state and prevent initState of been called more than once

I have 3 page (all statefull widgets) :

  • Home page
  • Weather page
  • Setting page

The things is when i'm going from home page to weather page with a "Navigator.pushNamed" and going from the weather page to home page with a "Navigator.pop", the next time i'm trying to go to the weather page from the home page, initState method is called again... How i can manage to make it call only the first time and not been called every time i push into the weather page ?

Here my app.dart code :

import 'package:exomind/src/core/views/home_view.dart';
import 'package:exomind/src/features/weather/presentation/views/weather_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import '../injection_container.dart';
import 'core/styles/colors.dart';
import 'features/settings/presentation/bloc/settings_bloc.dart';
import 'features/settings/presentation/views/settings_view.dart';
import 'features/weather/presentation/bloc/weather_bloc.dart';

/// The Widget that configures your application.
class MyApp extends StatelessWidget {
  const MyApp({
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // Glue the SettingsController to the MaterialApp.
    //
    // The AnimatedBuilder Widget listens to the SettingsController for changes.
    // Whenever the user updates their settings, the MaterialApp is rebuilt.

    return MultiBlocProvider(
        providers: [
          BlocProvider<WeatherBloc>(
              create: (_) => serviceLocator<WeatherBloc>()),
          BlocProvider<SettingsBloc>(
              create: (_) => serviceLocator<SettingsBloc>()
                ..add(
                  const SettingsLoaded(),
                )),
        ],
        child:
            BlocBuilder<SettingsBloc, SettingsState>(builder: (context, state) {
          return MaterialApp(
            debugShowCheckedModeBanner: false,

            // Providing a restorationScopeId allows the Navigator built by the
            // MaterialApp to restore the navigation stack when a user leaves and
            // returns to the app after it has been killed while running in the
            // background.
            restorationScopeId: 'app',

            // Provide the generated AppLocalizations to the MaterialApp. This
            // allows descendant Widgets to display the correct translations
            // depending on the user's locale.
            localizationsDelegates: const [
              AppLocalizations.delegate,
              GlobalMaterialLocalizations.delegate,
              GlobalWidgetsLocalizations.delegate,
              GlobalCupertinoLocalizations.delegate,
            ],
            supportedLocales: const [
              Locale('en', ''), // English, no country code
            ],

            // Use AppLocalizations to configure the correct application title
            // depending on the user's locale.
            //
            // The appTitle is defined in .arb files found in the localization
            // directory.
            onGenerateTitle: (BuildContext context) =>
                AppLocalizations.of(context)!.appTitle,

            // Define a light and dark color theme. Then, read the user's
            // preferred ThemeMode (light, dark, or system default) from the
            // SettingsController to display the correct theme.
            theme:
                ThemeData(fontFamily: 'Circular', primaryColor: kPrimaryColor),
            darkTheme: ThemeData.dark(),
            themeMode: state.themeMode,

            // Define a function to handle named routes in order to support
            // Flutter web url navigation and deep linking.
            onGenerateRoute: (RouteSettings routeSettings) {
              return MaterialPageRoute<void>(
                settings: routeSettings,
                builder: (BuildContext context) {
                  switch (routeSettings.name) {
                    case SettingsView.routeName:
                      return const SettingsView();
                    case WeatherView.routeName:
                      return const WeatherView();
                    case HomeView.routeName:
                      return const HomeView();
                    default:
                      return const HomeView();
                  }
                },
              );
            },
          );
        }));
  }
}

Here my home_view.dart code :

import 'package:flutter/material.dart';

import '../../features/weather/presentation/views/weather_view.dart';

class HomeView extends StatefulWidget {
  const HomeView({Key? key}) : super(key: key);
  static const routeName = '/home';

  @override
  State<HomeView> createState() => _HomeViewState();
}

class _HomeViewState extends State<HomeView>
    with SingleTickerProviderStateMixin {
  late AnimationController rotationController;

  @override
  void initState() {
    rotationController =
        AnimationController(duration: const Duration(seconds: 1), vsync: this)
          ..repeat();
    super.initState();
  }

  @override
  void dispose() {
    rotationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final double height = MediaQuery.of(context).size.height;
    final double width = MediaQuery.of(context).size.width;

    return Scaffold(
      body: Stack(
        alignment: Alignment.center,
        children: [
          Positioned(
            top: (height / 2),
            child: RotationTransition(
              turns: Tween(begin: 0.0, end: 1.0).animate(rotationController),
              child: IconButton(
                icon: const Icon(Icons.wb_sunny),
                color: Colors.yellow,
                iconSize: (width * 0.2),
                onPressed: () {
         Navigator.of(context).pushNamed(WeatherView.routeName);
                },
              ),
            ),
          )
        ],
      ),
    );
  }
}

Here my weather_view.dart code :

import 'dart:async';
import 'package:exomind/src/features/weather/presentation/bloc/weather_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:percent_indicator/percent_indicator.dart';

class WeatherView extends StatefulWidget {
  const WeatherView({Key? key}) : super(key: key);
  static const routeName = '/weather';

  @override
  State<WeatherView> createState() => _WeatherViewState();
}

class _WeatherViewState extends State<WeatherView>
    with SingleTickerProviderStateMixin {

  @override
  void initState() {
    print("initcalled")
    super.initState();
  }

  @override
  void dispose() {
    rotationController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);

    final double width = MediaQuery.of(context).size.width;
    final double height = MediaQuery.of(context).size.height;

    return Scaffold();
  }
}

Any help and explanation would be appreciate :)



Solution 1:[1]

As @RoslanAmir said there is no way to prevent initstate of been called each time we push into a statefulwidget. So to prevent my event of being added into my bloc each time we push into the stateful widget i add a bool variable to each state to know if the event should be added or not again. For those who want a precise answer don't hesitate.

Solution 2:[2]

I can't think of a "clean" way of not executing the initState in _WeatherViewState. Are you trying to avoid the same city added to the WeatherBloc more than once? If so, I'd check for the existence of 'city' in the WeatherBloc before adding.

Solution 3:[3]

In your onGenerateRoute you call the WeatherView constructor each time:

case WeatherView.routeName:
  return const WeatherView();

This in turn will call initState. What you need to do is create the WeatherView page widget once and use it in the onGenerateRoute:

final _weatherView = const WeatherView();

In your onGenerateRoute:

case WeatherView.routeName:
  return _weatherView;

Solution 4:[4]

Just add a parameter to the Weather page: a boolean that specifies if the rebuild is true or false. (If true, it will call the initState())

This code works fine.

class WeatherView extends StatefulWidget {
  final bool rebuild;
  static const routeName = '/weather';

  WeatherView({
    Key? key,
    required this.rebuild,
  }) : super(key: key);

  @override
  State<WeatherView> createState() => _WeatherViewState();
}

and the WeatherViewState's initState() will be:

@override
void initState() {
  if (widget.rebuild) {
    print("initcalled");
    super.initState();
  } else {
    print("Not called");
  }
}

So, in your app.dart you should now route to the page by doing

case WeatherView.routeName:
     return const WeatherView(rebuild: true); //Choose if rebuild or not by true and false

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jeremy Dormevil
Solution 2 mmaitlen
Solution 3
Solution 4