Flutter’da Provider Kullanarak Basit Bir Todo Uygulaması Geliştirme

Ali ATEŞ
Ali ATEŞ
Sep 17, 2019 · 5 min read

Merhaba, Google I/O’19 da duyurulan en güncel Flutter State yönetimi olan Provider yaklaşımının dev.to/shakib609 sitesindeki makalenin türkçe çevirisidir. ( https://dev.to/shakib609/create-a-todos-app-with-flutter-and-provider-jdh)

$ flutter create todos

• Kullandığınız derleyiciden todos adında bir flutter projesi oluşturduktan sonra proje ana dizinindeki pubspec.yaml klasörünü açıyoruz.
• Dependencies’in altına provider: ^3.1.0 yazarak provider’ı projeye ekleyelim.

dependencies:
flutter:
sdk: flutter
provider: ^3.1.0

Modelin Oluşturulması

• Uygulamamızda tek bir model ile çalışacağız. Çok basit bir uygulama olarak tek ihtiyacımız olan görevlerin başlığı (title) ve yapılıp yapılmadığı (completed).
• Klasör organizasyonu için ana dizinde lib/models klasörü ve içerisine de task.dart dosyası oluşturalım. Aşağıdaki gibi kodladığımız task.dart dosyasında completed değişkeni eğer kurucu fonksiyona gönderilmez ise false olarak Kabul edilmekte.

import ‘package:flutter/material.dart’;
class Task {
String title;
bool completed;
Task({@required this.title, this.completed = false});
void toggleCompleted() {
completed = !completed;
}
}

TodosModel Provider

• lib/providers/todos_model.dart dosyasını oluşturalım.
• Oluşturacağımız TodosModel sınıfı ChangeNotifier sınıfından kalıtım alacak ve bu modelimiz uygulamamızın state’ini değiştirmede bize yardım edecek, aynı zamanda uygulama yeniden render edildiğinde flutter’ı uyaracak.
• Getter’lar için dart:collection içerisindeki UnmodifiableListView kullanıyoruz. Bu, getter’ların TodosModel bildiriminin dışından hiçbir şekilde manipüle edilememesini sağlamak içindir.
• Dikkat edebileceğiniz bir diğer önemli şey, notifyListeners’ın sık sık kullanılmasıdır. Bu yöntem, durum değişikliğinin kullanıcı arayüzünün yeniden oluşturulmasını gerektirip gerektirmediğini flutter’a bildirir.
• Yalnızca provider’ı dinleyen UI widget’ı yeniden oluşturulacaktır.

import ‘dart:collection’;
import ‘package:flutter/material.dart’;
import ‘package:todos/models/task.dart’;
class TodosModel extends ChangeNotifier {
final List<Task> _tasks = [];

UnmodifiableListView<Task> get allTasks => UnmodifiableListView(_tasks);
UnmodifiableListView<Task> get incompleteTasks =>
UnmodifiableListView(_tasks.where((todo) => !todo.completed));
UnmodifiableListView<Task> get completedTasks =>
UnmodifiableListView(_tasks.where((todo) => todo.completed));

void addTodo(Task task) {
_tasks.add(task);
notifyListeners();
}

void toggleTodo(Task task) {
final taskIndex = _tasks.indexOf(task);
_tasks[taskIndex].toggleCompleted();
notifyListeners();
}
void deleteTodo(Task task) {
_tasks.remove(task);
notifyListeners();
}
}

Son Olarak UI

• Bu son bölümde, UI’ın nasıl sunulduğunu ve önceki bölümde oluşturulan TodosModel’in nasıl bağlandığını inceleyeceğiz.
• Uygulama HomeScreen ve AddTaskScreen şeklinde iki ekrandan oluşmaktadır.
• HomeScreen; AllTasks, IncompleteTask, CompleteTasks tablarını içeren bir TabView içerecek.
• HomeScreen’e ait widget tree aşağıdaki şekildedir.

Image for post
Image for post
import ‘package:flutter/material.dart’;
import ‘package:provider/provider.dart’;
import ‘package:todos/models/task.dart’;
import ‘package:todos/providers/todos_model.dart’;
class TaskListItem extends StatelessWidget {
final Task task;
TaskListItem({@required this.task});
@override
Widget build(BuildContext context) {
return ListTile(
leading: Checkbox(
value: task.completed,
onChanged: (bool checked) {
Provider.of<TodosModel>(context, listen: false).toggleTodo(task);
},
),
title: Text(task.title),
trailing: IconButton(
icon: Icon(
Icons.delete,
color: Colors.red,
),
onPressed: () {
Provider.of<TodosModel>(context, listen: false).deleteTodo(task);
},
),
);
}
}

• Şimdi, daha önce oluşturulan TaskListItem widget’ini, ListView widget’ındaki görevlerin bir listesini göstermek için kullanacak TaskList widget’ını yine widgets klasörünün içerisinde task_list.dart adında oluşturalım.

import ‘package:flutter/material.dart’;
import ‘package:todos/models/task.dart’;
import ‘package:todos/widgets/task_list_item.dart’;
class TaskList extends StatelessWidget {
final List<Task> tasks;
TaskList({@required this.tasks});
@override
Widget build(BuildContext context) {
return ListView(
children: getChildrenTasks(),
);
}

List<Widget> getChildrenTasks() {
return tasks.map((todo) => TaskListItem(task: todo)).toList();
}
}

• Şimdi HomeScreen widget’ımız için gerekli tüm tab’ları oluşturmaya hazırız.
• HomeScreen widget’ımız tab’larını aryrı dizinlerde tutacağız.
• İlk olarak lib/tabs/all_tasks.dart dosyasına AllTasksTab widget’ımızı kodluyoruz.

import ‘package:flutter/material.dart’;
import ‘package:provider/provider.dart’;
import ‘package:todos/providers/todos_model.dart’;
import ‘package:todos/widgets/task_list.dart’;
class AllTasksTab extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Consumer<TodosModel>(
builder: (context, todos, child) => TaskList(
tasks: todos.allTasks,
),
),
);
}
}

• Consumer, provider tarafından sağlanan yeni bir widget provider’dır. Bu widget, provider state’teki değişiklikleri dinlemek ve buna göre yeniden renderlamak için kolay bir yol sağlar.
• Bir Consumer widget’ına çok büyük bir widget ağacını dahil etmek genellikle kötü bir uygulama olarak kabul edilir. Gereksiz yeniden oluşturma işlemlerini önlemek için bu widget’ın widget ağacında olabildiğince derine yerleştirilmesi gerekir. Daha fazla bilgi için buraya bakınız.
• Task itemlerinden herhangi birinin değişmesi durumunda tüm liste öğelerini yeniden oluşturmamız gerekir. Bu yüzden Consumer içerisindeki TaskList’in kullanımına dahil ettik.
• Şimdi ne zaman provider’ımız modelindeki notifyListener’ı çağırırsa TaskList widget’ımız tekrar renderlanacak.
• Benzer şekilde, kalan iki tab widget’ını completed_tasks.dart ve incomplete_tasks.dart şeklinde widget klasörüne oluşturalım. (lib/tabs/completed_tasks.dart ve lib/tabs/uncompleted_tasks.dart)

// lib/tabs/complete_tasks.dart
import ‘package:flutter/material.dart’;
import ‘package:provider/provider.dart’;
import ‘package:todos/providers/todos_model.dart’;
import ‘package:todos/widgets/task_list.dart’;
class CompletedTasksTab extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Consumer<TodosModel>(
builder: (context, todos, child) => TaskList(
tasks: todos.completedTasks,
),
),
);
}
}
// lib/tabs/incomplete_tasks.dart
import ‘package:flutter/material.dart’;
import ‘package:provider/provider.dart’;
import ‘package:todos/providers/todos_model.dart’;
import ‘package:todos/widgets/task_list.dart’;
class IncompleteTasksTab extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Consumer<TodosModel>(
builder: (context, todos, child) => TaskList(
tasks: todos.incompleteTasks,
),
),
);
}
}

• Şimdi lib/screens/ home_screen.dart dosyasını oluşturalım ve HomeScreen widget’ımızı kodlayalım. Bu widget, uygulamamız için önceki oluşturduğumuz widget’ları kullanacak.

import ‘package:flutter/material.dart’;
import ‘package:todos/tabs/all_tasks.dart’;
import ‘package:todos/tabs/completed_tasks.dart’;
import ‘package:todos/tabs/incomplete_tasks.dart’;
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen>
with SingleTickerProviderStateMixin {
TabController controller; @override
void initState() {
super.initState();
controller = TabController(length: 3, vsync: this);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(‘Todos’),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: () {
},
),
],
bottom: TabBar(
controller: controller,
tabs: <Widget>[
Tab(text: ‘All’),
Tab(text: ‘Incomplete’),
Tab(text: ‘Complete’),
],
),
),
body: TabBarView(
controller: controller,
children: <Widget>[
AllTasksTab(),
IncompleteTasksTab(),
CompletedTasksTab(),
],
),
);
}
}

• Uygulama, tamamlanan ve tamamlanmayan taskları şimdi doğru bir şekilde görüntülemeli ve bir taskın tamamlanan özelliğinin durumunu değiştirebilmelisiniz.
• Ancak henüz uygulamamızı test edemiyoruz. Uygulamamızın çalışıp çalışmadığını kontrol etmek için bazı demo tasklara ihtiyacımız var.
• Şimdi task eklemek için provider/todos_model.dart dosyasını açalım ve birkaç Task model örneğini _tasks propertisine ekleyelim.

final List<Task> _tasks = [
Task(title: 'Finish the app'),
Task(title: 'Write a blog post'),
Task(title: 'Share with community'),
];

• Şimdi uygulamanıza bir göz atın. Tüm task’lar tamamlanmamış olmalı. Onay kutusuna dokunarak onları değiştirmeyi deneyin. UI aracılığıyla oluşturduğumuz task’ları güncelleyebilmeli, silebilmelisiniz.
• Şimdi, yapmamız gereken son şey, uygulamamıza task’lar eklemek için bir ekran oluşturmak. Bu işlevselliği kullanıcılarımıza sunmak için basit bir AddTaskScreen stateful widget’ı oluşturacağız.
• lib/screens/add_task_screen.dart dosyasını oluşturuyoruz.
• Stateful bir widget kullanıyoruz, çünkü bu widget’tan yeni task’lar oluştururken TextField ve Checkbox widgetlarının değerlerine ihtiyacımız var.

import ‘package:flutter/material.dart’;
import ‘package:provider/provider.dart’;
import ‘package:todos/providers/todos_model.dart’;
import ‘package:todos/models/task.dart’;
class AddTaskScreen extends StatefulWidget {
@override
_AddTaskScreenState createState() => _AddTaskScreenState();
}
class _AddTaskScreenState extends State<AddTaskScreen> {
final taskTitleController = TextEditingController();
bool completedStatus = false;
@override
void dispose() {
taskTitleController.dispose();
super.dispose();
}
void onAdd() {
final String textVal = taskTitleController.text;
final bool completed = completedStatus;
if (textVal.isNotEmpty) {
final Task todo = Task(
title: textVal,
completed: completed,
);
Provider.of<TodosModel>(context, listen: false).addTodo(todo);
Navigator.pop(context);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(‘Add Task’),
),
body: ListView(
children: <Widget>[
Padding(
padding: EdgeInsets.all(15.0),
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextField(controller: taskTitleController),
CheckboxListTile(
value: completedStatus,
onChanged: (checked) => setState(() {
completedStatus = checked;
}),
title: Text(‘Complete?’),
),
RaisedButton(
child: Text(‘Add’),
onPressed: onAdd,
),
],
),
),
)
],
),
);
}
}

• TodosModel’deki addTodo yöntemini çağırmak için provider paketindeki Provider.of<TodosModel>(context, listen: false)’ı tekrar kullandık.
• Bu, uygulama state’inin (durumunun) değişmesini ve tüm listening (dinleme) widget’larının bu değişiklikten haberdar edilmesini ve yeniden renderlanmasını sağlar.
• Şimdi, tek yapmamız gereken bu ekranı HomeScreen widget’ımızla bağlamak.
• lib/screens/home_screen.dart dosyanızı açın ve bunu içermesi için IconButton widget’larındaki onPressed argümanını güncelleyin.

import ‘package:flutter/material.dart’;
import ‘package:todos/screens/add_task_screen.dart’;


onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddTaskScreen(),
),
);
},

• Bu, Appbar’da + düğmesine her basıldığında kullanıcıyı AddTaskScreen’e götürür.
• Şimdi, tek yapmamız gereken lib/main.dart ana widget’ımızın içerisine return etmesi için bir ChangeNotifierProvider widget’ı eklemek.
• Bu widget bir TodosModel örneğimizi uygulamamızdaki tüm widget’lara iletecek.
• lib/main.dart dosyasını aşağıdaki gibi güncelleyelim.

import ‘package:flutter/material.dart’;
import ‘package:provider/provider.dart’;
import ‘package:todos/screens/home_screen.dart’;
import ‘package:todos/providers/todos_model.dart’;
void main() => runApp(TodosApp());
class TodosApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
builder: (context) => TodosModel(),
child: MaterialApp(
title: ‘Todos’,
theme: ThemeData.dark(),
home: HomeScreen(),
),
);
}
}

Now, our app is ready.
( https://github.com/shakib609/todos-flutter)

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store