Flutter MVVM ile Mobx State Yönetimi

Samet Karavaizoğlu
Berkut Teknoloji
Published in
5 min readSep 23, 2021

Herkese selamlar. Yazılacak kodun okunurluğunu ve kalitesini arttırmayı amaçlayan MVVM mimarisine ve Mobx State yönetimine bir giriş yapacağız.

Yazılım geliştirme sürecinde proje büyüdükçe yönetilmesi gereken kodlar da artar. Bu süreçte bir mimari kullanılmamışsa projede spagetti kodların ve bolca kod tekrarının olduğunu hepimiz biliriz.

MVVM mimarisi kod karmaşıklığını engellemek, okunabilirliğini artırmak ve kodların test edilmesini kolaylaştırmak için kullanılır.

Flutter ile uygulama geliştirirken ekranda güncelleme yapmak istediğimizde bir StatefulWidget oluşturup setState kullanarak bu işlemi yapabiliyoruz. Fakat setState kullanmak demek tüm sayfanın(build) yeniden çizilmesi demektir. Ekranda sadece belirli bir Text, Button gibi bir Widget’ da değişiklik olmasını istiyorsak veya bu değişiklikten diğer sayfaların da haberdar olmasını sağlamak için State Management kullanmak daha performanslı ve okunaklı olacaktır. Bu sayede tüm sayfayı yeniden çizmek yerine sadece değiştirmek istediğimiz Widget’ ı yeniden çizmiş olacağız.

Flutter Resmi Dökümanından

State Management için provider, mobx, bloc, riverpod gibi birçok paket bulunmakta. Her paketin kendine özgü kullanım farklılıkları olsa da hepsinin temel amacı state yönetimini sağlamaktır.

Bu yazımda Flutter ile basit bir post gösterim uygulaması yazacağız, ben önden kaynak kodları göreyim diyorsan buradan kodlara ulaşabilirsin.

Başlayalım!

mvvm_mobx_example adında bir Flutter projesi oluşturuyorum. pubspec.yaml dosyamıza mobx kullanımı için gerekli olan paketlerimizi ekliyoruz.

flutter_mobx: ^2.0.2

build_runner: ^2.1.2

mobx_codegen: ^2.0.0

build runner ve mobx_codegen paketlerini dev_dependencies altına ekleyebilirsiniz. Proje dizininde model, view ve view_model adında 3 klasör oluşturuyorum ve MyHomePage widget’ımı home_view.dart içine taşıyorum.

Servis çağrılarını yapmak için bir de network/service.dart oluşturuyoruz.

Amacımız home_view ekranındaki FloatingActionButton’ a tıkladığımızda viewModel’in servise istek atıp gelen response datasını post_model e map etmesini sağlamak ve bu modelin ui(home_view)’a dönmesini sağlamak. Servis istekleri için pubspec.yaml dosyamıza bir de

http: ^0.13.3

paketini de ekliyoruz.

İsteklerimizi yönetmek için bir servis sınıfı oluşturacağım. Servisten dönen değeri model sınıfında tutmak istediğim için servis çıktısını app.quicktype.io adresine yazarak hızlıca model sınıfımı oluşturuyorum. Model oluşturma işlemini yazarak da yapabilirsiniz, tercih sizin. Projeyi null-safe kullandığım için değişken tiplerinin sonuna soru işareti ekleyerek null gelebileceklerini ifade ediyorum.

// To parse this JSON data, do
//
// final postModel = postModelFromJson(jsonString);

import 'dart:convert';

PostModel postModelFromJson(String str) => PostModel.fromJson(json.decode(str));

String postModelToJson(PostModel data) => json.encode(data.toJson());

class PostModel {
PostModel({
this.userId,
this.id,
this.title,
this.body,
});

int? userId;
int? id;
String? title;
String? body;

factory PostModel.fromJson(Map<String, dynamic> json) => PostModel(
userId: json["userId"],
id: json["id"],
title: json["title"],
body: json["body"],
);

Map<String, dynamic> toJson() => {
"userId": userId,
"id": id,
"title": title,
"body": body,
};
}

Servis isteği atmak için sınıf yapım da bu şekilde.

import 'dart:io';
import 'package:http/http.dart' as http;
import '../consts/app_constants.dart';
class Service {
static Future<dynamic> get(
String path,
) async {
final response = await http.get(
Uri.parse("${ApplicationConstants.BASE_URL}$path"),
);
switch (response.statusCode) {
case HttpStatus.ok:
return response.body;
default:
throw response.body;
}
}
}

home_view_model.dart dosyasında mobx yazdıktan sonra çıkan snippetlerden MobX Store olanı seçiyorum ve adını HomeViewModel olarak tanımlıyorum.

import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart';
import '../model/post_model.dart';
import '../network/service.dart';
part 'home_view_model.g.dart';

class HomeViewModel = _HomeViewModelBase with _$HomeViewModel;

abstract class _HomeViewModelBase with Store {
@observable
PostModel? post;

int postId = 1;

@action
getPost() {
try {
Service.get('posts/$postId').then(
(response) {
post = postModelFromJson(response);
postId++;
},
);
} catch (exception) {
debugPrint(exception.toString());
}
}
}

part kısmında henüz .g.dart uzantılı dosya oluşturulmadığı için hata uyarısı veriyor, bu kısımda build_runner i devreye sokup dosyanın oluşturulmasını sağlayacağız.

VsCode da “build_runner watch” butonuna bastığımızda tetiklenmez ise

flutter pub get && flutter pub run build_runner build — delete-conflicting-outputs

komutuyla da build_runner’ ı tetikleyebilirsiniz. Artık .g.dart uzantılı dosyamız oluşturuldu.

Mobx bizlere 3 farklı annotation kullanımı sunuyor. Bunlar,

1- observable

Bir değişkene erişmek istiyorsak bu değişkene observable annotation’u eklemeliyiz.

@observable
int x = 10;

2- action

Bir fonksiyon çağıracaksak buna action annotation’u eklemeliyiz.

@action
increment(){
x = x + 1;
}

3- computed

Eğer bir işlem sonucunu döndürecek isek computed annotation kullanılır.

@computed
bool get isEven => number % 2 == 0 ? true : false;

Artık home_view.dart dosyamızdan viewModele erişebilir ve post getirme metodu çağırabilir haldeyiz. Hadi kodlayalım. Öncelikle build widgeti üzerine HomeViewModel viewModel = HomeViewModel(); ile viewModel sınıfıma erişim sağlıyoruz. Ve viewModel değişkeninden post modelimize viewModel.post şeklinde erişebiliyoruz.

Fakat burada önemli bir konu var. Eğer post verisi sürekli değişecek ve bu değişiklikleri dinlemek istiyorsak observable kullanıyoruz. Sabit kalacak ise herhangi bir annotation eklemeye gerek yoktur.

import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import '../view_model/home_view_model.dart';

class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);

final String title;

@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
HomeViewModel viewModel = HomeViewModel();

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Observer(
builder: (context) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
viewModel.post != null
? viewModel.post!.title!
: "İstek gönderilmedi",
style: Theme.of(context).textTheme.headline6,
),
Text(
viewModel.post != null ? viewModel.post!.body! : "",
),
],
),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
viewModel.getPost();
},
child: const Icon(Icons.add),
),
);
}
}

Column widgetini Observer ile sarmaladık peki ama neden? Çünkü içerisinde observable annotation’ı kullandığımız bir post modelindeki değişiklikleri dinlemek ve ona göre ekranda aksiyon almak istiyoruz.

Şimdilik benden bu kadar, umarım okumaktan keyif almışsınızdır.

Bir sonraki yazımda görüşmek üzere, sağlıcakla kalın.

Github: https://github.com/sametkaravaizoglu

Linkedin: https://www.linkedin.com/in/sametka/

--

--