본문 바로가기

Flutter/07 State - Provider

[Flutter] Provider - errors(provider with StatefulWidget)

오늘은 Provider 관련 에러들에 대해서 알아보겠습니다.

개발환경 : 윈도우11, 안드로이드 스튜디오(Arctic Fox 2020.3.1 Patch 4), flutter 2.10

소스코드 - https://github.com/mike-bskim/provider_overview

 

GitHub - mike-bskim/provider_overview

Contribute to mike-bskim/provider_overview development by creating an account on GitHub.

github.com

 

Provider 를 사용하지만 경우에 따라서 StatefulWidget 과 같이 사용하는 경우가 있다.

이럴때 흔히 만나는 에러 메시지중에 아래와 같은 메시지가 있다.

 

======== Exception caught by foundation library ========
The following assertion was thrown while dispatching notifications for Counter:
setState() or markNeedsBuild() called during build.

 

위젯을 빌드하는중에 initState() 에서 값이 변경되어 Provider에서 다시 위젯을 그리려할때 발생하는 오류 메시지이다.

이때 "addPostFrameCallback" 을 사용하면 오류를 피할수 있다.

해당 오류를 피하는 방법은 2가지가 더 있다. 자세한 사항은 소스코드를 참고하세요.

 

프로젝트 구성은 다음과 같다.

  • main.dart
  • ./models/counter.dart
  • ./pages/counter_page.dart

main.dart - 자세한 코드는 더보기 클릭

더보기
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'pages/counter_page.dart';
import 'pages/handle_dialog_page.dart';
import 'pages/navigate_page.dart';
import 'models/counter.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<Counter>(
      create: (_) => Counter(),
      child: MaterialApp(
        title: 'addPostFrameCallback',
        debugShowCheckedModeBanner: false,
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const MyHomePage(),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 30.0),
          child: ListView(
            shrinkWrap: true,
            children: [
              ElevatedButton(
                child: const Text(
                  'Counter Page',
                  style: TextStyle(fontSize: 20.0),
                ),
                onPressed: () => Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (_) => const CounterPage(),
                  ),
                ),
              ),
              const SizedBox(height: 20.0),
              ElevatedButton(
                child: const Text(
                  'Handle Dialog Page',
                  style: TextStyle(fontSize: 20.0),
                ),
                onPressed: () => Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (_) => const HandleDialogPage(),
                  ),
                ),
              ),
              const SizedBox(height: 20.0),
              ElevatedButton(
                child: const Text(
                  'Navigate Page',
                  style: TextStyle(fontSize: 20.0),
                ),
                onPressed: () => Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (_) => const NavigatePage(),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

 

counter.dart

 

import 'package:flutter/foundation.dart';

class Counter with ChangeNotifier {
  int counter = 0;

  void increment() {
    counter++;
    notifyListeners();
  }

  void update(int value) {
    counter = value;
    notifyListeners();
  }
}

 

counter_page.dart

 

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import '../models/counter.dart';

class CounterPage extends StatefulWidget {
  const CounterPage({Key? key}) : super(key: key);

  @override
  _CounterPageState createState() => _CounterPageState();
}

class _CounterPageState extends State<CounterPage> {
  int myCounter = 0;

  @override
  void initState() {
    super.initState();
    // after the current frame is completed, execute callback
    WidgetsBinding.instance!.addPostFrameCallback((_) {
      context.read<Counter>().increment();
      myCounter = context.read<Counter>().counter + 10;
    });
    // 2번째 오류 해결 방법
    // Future.delayed(const Duration(seconds: 0), () {
    //   context.read<Counter>().increment();
    //   myCounter = context.read<Counter>().counter + 10;
    // });
    // 3번째 오류 해결 방법
    // Future.microtask(() {
    //   context.read<Counter>().increment();
    //   myCounter = context.read<Counter>().counter + 10;
    // });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Counter'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'counter: ${context.watch<Counter>().counter}',
              textAlign: TextAlign.center,
              style: const TextStyle(fontSize: 40.0),
            ),
            Text(
              'myCounter: $myCounter',
              textAlign: TextAlign.center,
              style: const TextStyle(fontSize: 40.0),
            ),
            const SizedBox(
              height: 20.0,
            ),
            ElevatedButton(
              onPressed: () {
                myCounter = 10;
                context.read<Counter>().update(0);
              },
              child: const Text('Reset counter'),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          context.read<Counter>().increment();
        },
      ),
    );
  }
}