Flutter Provider 패키지의 가장 기본 사용방법

Flutter 앱을 만들면서 관리해야 하는 변수가 늘어가고 페이지 간 변수의 전달이 어려워 provider의 사용을 고민하고 계신가요? 이 글에서는 Provider 패키지의 가장 기본 사용방법에 대해서 알아보도록 하겠습니다. 기본부터 심화까지 앞으로의 연재글을 통해서 정리하시기 바랍니다.

이 글은 이전 글 ChangeNotifier, addListener를 이용해 앱 상태관리하기와 이어지는 글입니다.

이전 글에서는 flutter native 패키지인 ChangeNotifier와 addListener의 사용을 통한 앱 상태관리 방법에 대해서 알아보았습니다. ChangeNotifier를 이용하기 위해서 ChangeNotifier를 상속받고 있는 별도의 클래스를 정의합니다. 이 클래스에는 상태관리 대상이 정의되어 있습니다. 그 대상은 List 목록이 될 수도 있고 변수가 될 수도 있습니다. 그리고 이 대상을 관리할 수 있는 다양한 메서드들 또한 정의하게 되는데 값을 조회한다던지 수정하는 등의 작업을 정의할 수 있습니다.

Provider를 이용하는 방법은 크게 3단계로 구분할 수 있습니다.

  1. 상태관리를 할 대상을 클래스로 따로 정의합니다. 이 클래스는 ChangeNotifier를 상속합니다.
  2. Provider를 이용할 위젯의 최상위를 Provider Widget으로 감싸줍니다.
  3. 상태관리를 할 변수나 상태관리가 필요한 이벤트에 Provider를 적용합니다.

이 글에서는 가장 간단한 형태의 counter 앱을 Provider를 이용해서 만들어 보도록 하겠습니다. counter앱을 플러스(+) 버튼을 누르면 숫자가 1증가하고 마이너스(-) 버튼을 누르면 숫자가 1 감소하는 기능을 가집니다.

상태관리 클래스 정의

Provider를 구현할 때 가장 먼저 할 작업은 무엇을 상태관리 할 것인지를 결정하는 것입니다. counter 앱은 버튼을 누를때마다 바뀌는 숫자값이 상태관리의 대상이 됩니다. 이 대상은 한 개의 변수일 수도 있고 여러개일 수도 있습니다. 여기에서는 count라는 변수를 관리하도록 하겠습니다.

그리고 count 변수의 상태변화를 야기할 수 있는 이벤트를 정의합니다. 플러스 버튼을 누를 때 실행되는 함수인 add와 마이너스 버튼을 누를 때 실행되는 함수인 remove를 정의하겠습니다. Provider를 이용하여 이벤트를 정의할 경우 비지니스 로직과 UI를 분리할 수 있다는 장점을 가지게 됩니다. 즉 계산은 Provider 클래스에서 수행하고 화면 위젯에서는 변수만 보여주면 되는 것이죠.

ChangeNotifier 타입의 클래스에서의 특징은 메서드 안에 notifyListeners()를 콜한다는 것입니다. notifyListeners는 상태관리 대상의 상태가 메서드에 의해서 변경이 될 경우 이 변경된 내용을 알려주는 전달자의 역할을 하게 됩니다. 그래서 변경된 내용에 맞춰서 UI를 변경한다던가 리스트를 업데이트 한다던가 등의 작업을 할 수 있게 됩니다.

그리고 변수에는 직접 접근하도록 하지 말고 private 변수를 정의한 다음 이를 접근할 수 있는 get 함수를 별도로 정의하는 습관을 기르도록 합시다. 이렇게 정의한 CountProvider 클래스는 다음과 같습니다.

lib/provider/count_provider.dart

import 'package:flutter/material.dart';

class CountProvider extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  add() {
    _count++;
    notifyListeners();
  }

  remove() {
    _count--;
    notifyListeners();
  }
}

Provider 위젯 적용

Provider 위젯을 이용하기 위해서는 context에 접근할 수 있어야 합니다. context는 위젯 구조 상 가장 상위에 있는 같은 형식의 context에 접근할 수 있는 방법이다 정도로 이해하도록 하겠습니다. context를 정의함으로써 우리는 그 하위 위젯 중 동일한 타입의 context를 가지고 있는 위젯에서 언제든 최상위 위젯에 접근할 수 있게 됩니다. Provider는 상태관리가 필요한 변수들을 최상위 context에 정의함으로써 하위의 어느 위젯에서든지 동일한 context를 불러들일 수 있도록 한 것입니다. 그래서 Provider 위젯은 상태관리를 할 변수들을 하위에 포함할 수 있는 위치이면 됩니다. 보통은 main 바로 아래에 위치시키지만 꼭 최상위에 있을 필요는 없습니다.

lib/main.dart

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

import 'provider/count_provider.dart';
import 'route/route_generator.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
        create: (BuildContext context) => CountProvider(),
        child: const MyApp(),),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.green,
      ),
      // home: const MyHomePage(title: 'Flutter Demo Home Page'),
      initialRoute: '/counter',
      onGenerateRoute: RouteGenerator.generateRoute,
    );
  }
}

변수나 이벤트에 Provider 적용

Provider 클래스를 정의하였고, UI상 가장 상위에 Provider 위젯으로 감싸주었습니다. 이제 실제 변수가 사용되는 위치에서 Provider를 이용하여 변수를 불러오면 됩니다. 변수를 불러오는 방벙은 Provider.of<T>를 이용해주면 됩니다.

lib/screens/counter.dart

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

import '../provider/count_provider.dart';

class Counter extends StatelessWidget {
  Counter({super.key});
  CountProvider? _countProvider;

  @override
  Widget build(BuildContext context) {
    _countProvider = Provider.of<CountProvider>(context);
    return Scaffold(
      appBar: AppBar(
        title: const Text("Provider Sample"),
      ),
      body: Center(
        child: Text(Provider.of<CountProvider>(context).count.toString(),
            style: const TextStyle(fontSize: 24)),
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          IconButton(
            icon: const Icon(Icons.add),
            onPressed: () {
              _countProvider?.add();
            },
          ),
          IconButton(
            icon: const Icon(Icons.remove),
            onPressed: () {
              _countProvider?.remove();
            },
          ),
        ],
      ),
    );
  }
}

Leave a Comment