Search code examples
flutterdartstatefulwidget

Flutter MainApp as StatefulWidget leads to problems


I'm afraid I've totally lost my way as a beginner with my Flutter app. I come from the .NET c# world. The app is supposed to have multiple pages that can be switched back and forth between. For this purpose I have included a PageView.

However, I need to be able to pass data from one page to another. For this I have introduced a property currentArticleList and therefore had to convert the MainApp into a StatefulWidget. I think this was a mistake, because now the AppBar, for example, can't access the theme colors anymore. Also, I've read in several places that this is generally a bad practice, because it will always redraw the whole app.

The MainApp itself will never access the currentArticleList, by the way.

How can I fix the theme color accessing problem? Can I leave it as a StatefulWidget, or is there a better, more flutter-like solution? I've added some comments to the source code.

import 'package:flutter/material.dart';
import 'package:flutter_lss/pages/article_list.dart';
import 'package:flutter_lss/pages/loading_overlay.dart';
import 'package:flutter_lss/pages/scan_number.dart';
import 'package:flutter_lss/data/article.dart';

enum AppPage {
  scanNumber,
  articleList,
}

void main() {
  runApp(const MainApp());
}

class MainApp extends StatefulWidget {
  const MainApp({super.key});

  @override
  State<MainApp> createState() => _MainAppState();
}

class _MainAppState extends State<MainApp> {
  List<Article> currentArticleList = List.empty(); // This is the reason the MainApp must be stateful. Isn't there a better way to do this?
  final PageController _pageController = PageController();

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          // ...
        ),
        useMaterial3: true,
      ),
      home: LoadingOverlay(
        child: Scaffold(
          appBar: AppBar(
            backgroundColor: Theme.of(context).colorScheme.primary, // this returns the default colors instead of the ones set by me
            foregroundColor: Theme.of(context).colorScheme.onPrimary,
            title: const Text('My App'),
          ),
          body: PageView(
            controller: _pageController,
            children: [
              PageScanNumber(onArticlesFetched: _handleFetchedArticles),
              PageArticleList(articles: currentArticleList, onScanningComplete: _handleScanningComplete),
            ],
          ),
        ),
      ),
    );
  }

  void _handleFetchedArticles(List<Article> articles) {
    setState(() {
      currentArticleList = articles;
    });
    _animateToPage(AppPage.articleList);
  }

  void _handleScanningComplete() {
    _animateToPage(AppPage.scanNumber);
  }

  void _animateToPage(AppPage page) {
    _pageController.animateToPage(
      page.index,
      duration: const Duration(milliseconds: 300),
      curve: Curves.easeInOut,
    );
  }
}

Solution

  • You are trying to read theme within the same context you've provided. You can liftup the MaterialApp(context) so that it can get your provided theme data. Or just wrapping with Builder widget will work fine home: Builder(builder: (context) => LoadingOverlay(....),).

    void main() {
      runApp(
        MaterialApp(
          theme: ThemeData(
            colorScheme: ColorScheme.fromSeed(
                // ...
                ),
            useMaterial3: true,
          ),
          home: MainApp(),
        ),
      );
    }
    

    I would prefer creating widget for app and home separately.