티스토리 뷰
Provider
Provieder를 쓰는 이유
1. 코드역할 분리
UI를 담당하는 코드, 네트워크를 담당하는 코드, 데이터를 담당하는 코드 등
코드를 역할에 따라 나눌 수 있다.
한 클래스가 여러 역할을 할수록, 클래스가 커지고 관리가 어렵게 됩니다.
따라서 클래스가 하나의 역할만 갖도록, 클래스를 나눈다.
2. 데이터 공유
하나의 데이터를 여러 페이지에서 공유해야 될 때가 있다.
그런데 페이지마다 데이터를 새로 불러온다면 앱이 복잡해지고, 비용도 많이 들 것이다.
이럴 때 Provider 패턴을 쓰면 데이터 공유를 쉽게 할 수 있다.
3. 간결한 코드
Provider 패턴을 쓰면 Bloc패턴에 비해
좀 더 적은 코드로 클래스들을 구분해서 쓸 수 있다.
패키지 추가
provider: ^6.0.5
예제(TodoList)
provider 부분
class Task {
final String title;
bool isCompleted;
Task(this.title, this.isCompleted);
}
Task 클래스를 정의 해준다.
title: 할 일의 제목을 나타내는 문자열
isCompleted: 할 일 확인 여부를 나타내는 bool형 값
두 변수의 생성자를 초기화해준다.
class TaskList with ChangeNotifier {
final List<Task> _tasks = [];
List<Task> get tasks => _tasks;
void addTask(String title) {
_tasks.add(Task(title, false));
notifyListeners();
}
void toggleTask(int index) {
_tasks[index].isCompleted = !_tasks[index].isCompleted;
notifyListeners();
}
void removeTask(int index) {
_tasks.removeAt(index);
notifyListeners();
}
}
final List<Task> _tasks = []; 로
Task클래스를 이용해 리스트 타입의 할 일 목록 변수를 선언한다.
List<Task> get tasks => _tasks; 로
_tasks라는 프라이빗 변수를 다른 클래스에서도 쓸 수 있게 한다.
ChangeNotifier 클래스 내에서 상태를 변경하고,
변경 사항을 notifyListeners()를 호출하여 provider에 알린다.
이로써 UI를 업데이트하고 상태를 반영한다.
의존성 주입
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => TaskList(),
child: const MaterialApp(
title: 'Todo List',
home: TaskListScreen(),
),
);
}
}
ChangeNotifierProvider를 사용하여 앱의 다양한 부분에서 동일한 할 일 목록 데이터를 주입하고 공유합니다.
감시자(Listener) 등록
class TaskListScreen extends StatelessWidget {
const TaskListScreen({super.key});
@override
Widget build(BuildContext context) {
final taskList = Provider.of<TaskList>(context); // Listener
Provider.of<TaskList>(context)를 사용하여
TaskList 클래스의 인스턴스인 taskList를 가져온다.
taskList 객체는 TaskList 클래스의 상태를 감시하고,
상태가 변경될 때마다 해당 위젯을 다시 렌더링 하도록 강제한다.
provider 활용
return Scaffold(
appBar: AppBar(
title: const Text('Todo List'),
),
body: ListView.builder(
itemCount: taskList.tasks.length,
itemBuilder: (context, index) {
final task = taskList.tasks[index];
return ListTile(
title: Text(task.title),
leading: Checkbox(
value: task.isCompleted,
onChanged: (_) => taskList.toggleTask(index),
),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => taskList.removeTask(index),
),
);
},
),
이 부분의 코드는 TaskListScreen 위젯이 화면에 나타나면
할 일 목록을 동적으로 생성하여 보여주는 역할을 합니다.
provider에서 정의한 함수들과 변수를 활용한다.
floatingActionButton: FloatingActionButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
final TextEditingController controller = TextEditingController();
return AlertDialog(
title: const Text('Add Task'),
content: TextField(
controller: controller,
decoration: const InputDecoration(hintText: 'Task Name'),
),
actions: <Widget>[
TextButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('Add'),
onPressed: () {
taskList.addTask(controller.text);
Navigator.of(context).pop();
},
),
],
);
},
);
},
child: const Icon(Icons.add),
),
이 부분은 task를 추가하는 부분이다.
마찬가지로 provider를 활용해 구현했다.
BuildContext 활용
// context를 사용하여 할 일 목록 데이터를 가져옴
final taskList = Provider.of<TaskList>(context);
showDialog(
context: context, // context를 사용하여 다이얼로그를 띄움
builder: (context) {}
할 일 목록 데이터를 가져올 때와 다이얼로그를 띄울 때
context가 사용되었다.
BuildContext를 활용하여 현재 위젯의 위치와 정보를 알 수 있으며,
상태 관리와 UI 빌드에 사용됩니다.
MultiProvider
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class User extends ChangeNotifier {
final String name;
User(this.name);
}
class Settings extends ChangeNotifier {
final bool darkMode;
Settings(this.darkMode);
}
Provider가 여러 개 일 땐 MultiProvider를 쓴다.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<User>(create: (_) => User('John')),
ChangeNotifierProvider<Settings>(create: (_) => Settings(false)),
],
child: const MaterialApp(
home: HomeScreen(),
),
);
}
}
MultiProvider의 providers 속성에 원하는 프로바이더를 적어주면 된다.
providers: [
ChangeNotifierProvider<User>(create: (_) => User('John')),
ChangeNotifierProvider<User>(create: (_) => User('James')), // 이 값 가져옴
],
같은 제네릭 타입이나 자료형을 가질 경우엔 가장 밑의 값을 가져온다.
final user = Provider.of<User>(context);
final settings = Provider.of<Settings>(context);
Provider.of<클래스>(context)를 이용해 해당 클래스의 인스턴스를 가져올 수 있다.
함수 재구성
void deleteTodo(int id) {
dummyTodos.removeWhere((todo) => todo.id == id);
}
'removeWhere' 메서드를 사용해 id가 일치하는 todo를 제거한다.
void updateTodo(Todo updatedTodo) {
final index = dummyTodos.indexWhere((todo) => todo.id == updatedTodo.id);
if (index != -1) {
dummyTodos[index] = updatedTodo;
}
}
'removeWhere' 메소드를 사용해 id가 일치하는 todo를 수정한다.
id가 일치하는 todo가 없다면 index의 값은 -1이 된다.