Home > Back-end >  Preserve state and prevent initState of been called more than once
Preserve state and prevent initState of been called more than once

Time:04-20

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: [
          Container(
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(10),
              image: const DecorationImage(
                image: AssetImage('assets/images/cloud-in-blue-sky.jpg'),
                fit: BoxFit.cover,
              ),
            ),
          ),
          Positioned(
            top: (height / 2) - 100,
            child: Text(
              "Weather",
              style:
                  TextStyle(fontFamily: 'Billabong', fontSize: (width * 0.2)),
            ),
          ),
          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).push(
                  //   MaterialPageRoute(
                  //     builder: (context) => const WeatherView(),
                  //   ),
                  // );
                  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, AutomaticKeepAliveClientMixin {
  late AnimationController rotationController;

  @override
  void initState() {
    rotationController =
        AnimationController(duration: const Duration(seconds: 1), vsync: this)
          ..repeat();
    BlocProvider.of<WeatherBloc>(context)
        .add(const GetCurrentWeather(city: 'city'));
    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(
      backgroundColor: Colors.white,
      appBar: AppBar(
        leading: BackButton(
          color: Colors.black,
          onPressed: () => Navigator.of(context).pop(),
        ),
        elevation: 0,
        backgroundColor: Colors.white,
      ),
      body: BlocBuilder<WeatherBloc, WeatherState>(builder: (context, state) {
        if (state is WeatherLoading) {
          return Stack(
            children: [
              Center(
                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.pushNamed(context, WeatherView.routeName);
                    },
                  ),
                ),
              ),
              Align(
                alignment: Alignment.bottomCenter,
                child: Padding(
                  padding: EdgeInsets.only(bottom: height * 0.15),
                  child: Text(state.loadingMessage),
                ),
              ),
              Align(
                alignment: Alignment.bottomCenter,
                child: Padding(
                  padding: EdgeInsets.only(
                    left: width * 0.05,
                    right: width * 0.05,
                    bottom: height * 0.10,
                  ),
                  child: LinearPercentIndicator(
                    animation: true,
                    animationDuration: 60000,
                    percent: 1,
                    center: Text(
                      state.percent.toString()   "%",
                      style: const TextStyle(
                          fontSize: 12.0,
                          fontWeight: FontWeight.w600,
                          color: Colors.black),
                    ),
                    lineHeight: 20.0,
                    barRadius: const Radius.circular(10),
                    progressColor: Colors.blue[400],
                    backgroundColor: Colors.grey[300],
                  ),
                ),
              ),
            ],
          );
        } else if (state is WeatherLoaded) {
          return const Center(child: Text('Loaded'));
        } else if (state is WeatherError) {
          return Center(child: Text(state.message));
        } else {
          return const Center(child: Text('Else'));
        }
      }),
    );
  }

  @override
  bool get wantKeepAlive => true;
}

Any help and explanation would be appreciate :)

CodePudding user response:

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.

CodePudding user response:

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:

return _weatherView;
  • Related