Search code examples
fluttertreeduplicateswidget

Flutter prevent building a new widget if already in tree


For some reason, the Build method is called twice which results in two MainContent widgets being created. The problem is that in one of my widgets a Listener displays messages to the user according to certain actions. Because Maincontent is duplicated, messages are displayed twice.

widget tree

How to prevent the MainContent widget from being duplicated?

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  Bloc.observer = ProductBlocObserver();
  var productStorage = ProductStorage();
  await productStorage.products().then((localProducts) async {

    final prefs = await SharedPreferences.getInstance();
    await prefs.setBool('isMember', false);
    await prefs.setBool('membershipError', false);
    await prefs.setBool('firstOpen', true);
    await prefs.setString('memberFirstName', '');
    await prefs.setString('token', '');

    runApp(App(list: localProducts));

  });
}
class App extends StatelessWidget {
  List<LocalProductEntity> list;
  App({Key? key, required this.list}) : super(key: key) {
    list = list;
  }

  @override
  Widget build(BuildContext context) {

    return BlocProvider <ListBloc>(
      create: (_) => ListBloc(list: list),
      child: MaterialApp(
        debugShowCheckedModeBanner: true,
        title: constants.appTitle,
        theme: ThemeData(
          colorScheme: ColorScheme.fromSwatch().copyWith(
            primary: Color(ColorsLNC.green5),
            secondary: Color(ColorsLNC.green1),
          ),
        ),
        home: BlocPage()
    ),
    );
  }
}
class BlocPage extends StatelessWidget {

  final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
  late Future<bool> isMember;
  late Future<bool> membershipError;
  late Future<bool> firstOpen;
  late Future<String> memberFirstName;
  late Future<String> token;
  late BuildContext context;

  BlocPage({super.key});


  @override
  Widget build(BuildContext context) {
    this.context = context;
    isMember = _prefs.then((SharedPreferences prefs) {
      return prefs.getBool('isMember') ?? false;
    });
    membershipError = _prefs.then((SharedPreferences prefs) {
      return prefs.getBool('membershipError') ?? false;
    });
    firstOpen = _prefs.then((SharedPreferences prefs) {
      return prefs.getBool('firstOpen') ?? false;
    });
    memberFirstName = _prefs.then((SharedPreferences prefs) {
      return prefs.getString('memberFirstName') ?? '';
    });
    token = _prefs.then((SharedPreferences prefs) {
      return prefs.getString('token') ?? '';
    });

    return FutureBuilder(
        future: membershipFlow(),
        builder: (BuildContext context, AsyncSnapshot snapshot) {
          switch (snapshot.connectionState) {
            case ConnectionState.waiting:
              return const CircularProgressIndicator();
            default:
              return snapshot.data;
          }
        });
  }

  Future<Widget> membershipFlow() async {
    if (await isMember == true && await membershipError == false) {
      return SplashPage(
        mainContext : context,
        goToPage: MainContent(),
        text: await memberFirstName,
        duration: 2,
      );
    } else if (await isMember == false && await membershipError == false) {
      return const MembershipForm();
    } else if (await isMember == false && await membershipError == true) {
      return ErrorPage(text: Babel.translate(key: 'E_WRONG_MEMBERSHIP'));
    } else {
      return ErrorPage(text: Babel.translate(key: 'E_UNEXPECTED'));
    }
  }
}

Solution

  • Use const with both constructor and while using it.

    For example const BlocPage({super.key}) and const BlocPage().

    You code has different issue tough. You could refactor your code like this and build won't be called repeatedly.

    class BlocPage extends StatelessWidget {
      const BlocPage({super.key});
      @override
      Widget build(BuildContext context) {
        return FutureBuilder(
            future: membershipFlow(context),
            builder: (BuildContext context, AsyncSnapshot snapshot) {
              switch (snapshot.connectionState) {
                case ConnectionState.waiting:
                  return const CircularProgressIndicator();
                default:
                  return snapshot.data;
              }
            });
      }
    
      Future<Widget> membershipFlow(BuildContext context) async {
        final prefs = await SharedPreferences.getInstance();
        final isMember = prefs.getBool('isMember') ?? false;
        final membershipError = prefs.getBool('membershipError') ?? false;
        final firstOpen = prefs.getBool('firstOpen') ?? false;
        final memberFirstName = prefs.getString('memberFirstName') ?? '';
        final token = prefs.getString('token') ?? '';
    
        if (isMember == true && membershipError == false) {
          return SplashPage(
            mainContext : context,
            goToPage: MainContent(),
            text: memberFirstName,
            duration: 2,
          );
        } else if (isMember == false && membershipError == false) {
          return const MembershipForm();
        } else if (isMember == false && membershipError == true) {
          return ErrorPage(text: Babel.translate(key: 'E_WRONG_MEMBERSHIP'));
        } else {
          return ErrorPage(text: Babel.translate(key: 'E_UNEXPECTED'));
        }
      }
    }