Flutter build called multiple times
Asked Answered
C

4

11

I have a bottom nav bar that has a list page that uses a bloc for the state.

class _MainPageState extends State<MainPage> {
  int _index = 0;
  @override
  Widget build(BuildContext context) {
    final List<Widget> _widgets = [
      const ListPage(),
      Scaffold(),
      Scaffold(),
      
    ];

    return Scaffold(
        body: IndexedStack(
          index: _index,
          children: _widgets,
        ),
      bottomNavigationBar: BottomNavigationBar(
    ...


class ListPage extends StatelessWidget {
  const ListPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) =>
          getIt<ListBloc>()..add(const ListEvent.load(limit: 10)),
      child: SafeArea(
        child: Scaffold(
          appBar: AppBar(),
          body: const List(),
        ),
      ),
    );
  }
}

The problem is build is beeing called 4 times. This causes the event fetching the list 4 times.

Don't know where to add the event to prevent re-builds.

If I add the event in statefull widget's initState. Bloc does not recognize ListBloc beeing in the context when fetching the bloc down the widget tree.

Carnivorous answered 23/11, 2020 at 0:28 Comment(3)
Just out of curiosity, why do you have two Scaffolds nested in a parent Scaffold?Dialogist
They are placeholders. Still working on that part.Carnivorous
You should check this #52250078Biak
A
5

The best way is to add event in initState of you main route (Route that contains Bottom Navigation). As shown in below code.

class _MainPageState extends State<MainPage> {
  int _index = 0;

  @override
  void initState() {
    super.initState();
    getIt<ListBloc>().add(const ListEvent.load(limit: 10));
  }

  @override
  Widget build(BuildContext context) {
    final List<Widget> _widgets = [
      const ListPage(),
      Scaffold(),
      Scaffold(),
      
    ];

    return Scaffold(
        body: IndexedStack(
          index: _index,
          children: _widgets,
        ),
      bottomNavigationBar: BottomNavigationBar(
    ...


class ListPage extends StatelessWidget {
  const ListPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (_) =>
          getIt<ListBloc>(),
      child: SafeArea(
        child: Scaffold(
          appBar: AppBar(),
          body: const List(),
        ),
      ),
    );
  }
}
Apgar answered 27/11, 2020 at 19:47 Comment(3)
With this. initState is called twice.Carnivorous
No it will not.Apgar
If it solved your issue, please accept my answer then.Apgar
P
3

you can use StatefulWidget class with KeepAlive, according to official doc (Mark a child as needing to stay alive even when it's in a lazy list that would otherwise remove it).

In code :

class YourStatefulWidget extends StatefulWidget {

//add with AutomaticKeepAliveClientMixin to the state class

class _YourStatefulWidgetState extends State<YourStatefulWidget > with AutomaticKeepAliveClientMixin {

  //add  super.build(context) under the build method
  Widget build(BuildContext context) {
  super.build(context);

  return //your widget

   @override
   bool get wantKeepAlive => true;
  }
 }
}

Or you can fixed logically by using a Boolean value form your state management class as the value wouldn't change if the widget get rebuild.

Price answered 25/11, 2020 at 1:30 Comment(0)
E
3

I think the problem is in MainPage. When you change selected page in bottom navigation bar you forces the call of build of MainPage but it recreates _widgets variable every time and this forces update child widgets. Move final _widgets = ... in class scope, i.e. your variable will be created once.

Also you should provide Bloc on level above your bottom navigation bar item i.e. wrap ListPage with BlocProvider. And than it will be found in tree. Or try to use Builder between BlocProvider and SafeArea in ListPage.

To get ListBloc you need to use BlocProvider.of<ListBloc>(context) in a widget where you need bloc.

Epiphora answered 27/11, 2020 at 22:31 Comment(0)
T
2
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        
        primarySwatch: Colors.blue,
       
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page',con: context,), // passing context
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title, this.con}) : super(key: key); // receiving context
  final String title;
  
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
 
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
 
    return Scaffold(
      appBar: AppBar(
 
        title: Text(widget.title),
      ),
      body: Center(
 
        child: Column(
 
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(widget.con).textTheme.headline4, // using received context
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

 
}

Pass the context from one class to another instead of creating a new context every time.. The above example is a default flutter app created when you create a new flutter project. see the comments passing context, receiving context and using received context I think this should resolve the issue..

Tinfoil answered 25/11, 2020 at 7:45 Comment(2)
Can you explain why passing the context down prevents rebuild in the child. Or for that matter rebuild in the parent as I placed the create function in the parent and it didn't work.Carnivorous
flutter.dev/docs/perf/rendering/best-practices .. Generally speaking, there are 2 uses cases for context : 1. Interact with your parents (get/post data mostly) 2. Once rendered on screen, get your screen size and position. The second point is kinda rare. On the other hand, the first point is used nearly everywhere.Tinfoil

© 2022 - 2024 — McMap. All rights reserved.