Flutter’da GetX, GetX ile Obserable Kullanımı ve MVVM

Gökhan Alp
7 min readOct 25, 2021

--

Merhabalar,

Bu yazımızda Flutter ile MVVM Design Pattern kullanarak örnek bir proje geliştireceğiz ve bu projede GetX sayesinde observable iletişimden faydalanacağız. GetX’in getirdiği faydalar sadece observable ile kısıtlı kalmayacak, çeşitli katkılarına da değineceğim.

Öncelikle nedir bu GetX?

GetX pub.dev sayfasında kendini State & Navigation & Dependency Manager olarak tanımlar. Türkçeye çevirirsek durumu, geçişleri ve bağımlılıkları yönettiğini söyleyebiliriz. Yani bir sayfadaki değişiklikleri, sayfa geçişlerinizi yönetmenizi, verilerinizi pratik bir şekilde çekmenizi veya belirli bir controllerdan veriyi elde etmenize katkı sağlar.

Şuanda Flutter’da en çok beğenilen kütüphane olan GetX, kendini hızlı, stabil, ekstra hafif ve güçlü bir kütüphane olarak da tanımlamaktadır. Yani işin aslı mevcutta yapmanız gereken bir çok temel işi GetX kütüphanesi ile daha kolay ve etkin bir şekilde yapabilirsiniz.

GetX kütüphanesine ait link yazımızın sonunda altta paylaşılacaktır.

Nedir bu GetX’in faydaları?

Aşağıda maddeler ile yazılmış başlıklarda ve içeriklerinde bu faydalara değinilecektir. Yazı da tümüyle bu kütüphanenin size sağladığı faydaya odaklanılacak ve somut örneklerle detay bilgiler verilecektir. Gelelim faydalara;

- State Management

GetX ile UI’da birşeyleri değiştirmek hiç olmadığı kadar daha kolay. Artık yok statefull mu stateless mi düşünmeye son, setState yazma derdine de son. Bir şeyleri değiştirmek ve gösterimini sağlamak alttaki örnek gibi çok kolay.

Obx(() => Text(
'Obx: ${MyApp.counter}'
),),
FlatButton(onPressed: () => { controller.increment() }, child: Text("+"),)

State Management ile pek çok yararlı ek imkanlara sahip olmaktasınız. Bunlardan en önemlisi olan Observable konusuna ve Reactive kullanımına makalenin devamında kapsamlı bir şekilde değinilmiştir. Diğer faydalarını için şu linki inceleyebilirsiniz;

https://github.com/jonataslaw/getx/blob/master/documentation/en_US/state_management.md

- Navigation Management

Navigation’u yönetmek GetX ile son derece kolaydır. Altta detaylandırılan çeşitli faydalar sağlamaktadır.

Sayfalarınızın bindinglerini ayarlalyıp geçişleriinin nasıl olacağını ve ek detaylarını önceden ayarlayabilirsiniz.

class MyApp extends StatelessWidget {
...
@override
Widget build(BuildContext context) {
return GetMaterialApp(
...
getPages: AppPages.pages,
);
}
...
}

GetPages dizinini AppPages isimli bir classta toplayarak kullandım;

class AppPages {
static final pages = [
GetPage(
name: AppRoutes.splash,
page: () => SplashView(),
),
GetPage(
name: AppRoutes.home,
page: () => HomeView(),
binding: HomeBinding()
),
GetPage(
name: AppRoutes.nextDetail,
page: () => NextDetailView(),
binding: NextDetailBinding(),
transition: Transition.zoom
),

];
}

Başka bir sayfaya belirli bir hiyerarşik rotada isim ile toNamed kullanarak erişebilirsiniz.

Get.toNamed('/home/nextDetail');

İsimle erişmek istemiyorsanız ve direkt page ile erişecekseniz to(..) kullanarak GetPage(..) ile sayfaya gidebilirsiniz.

Çok pratik bir şekilde sayfalar arası sanki bir url varmış gibi bilgi göndermeniz de mümkündür. Örneğin;

GetPage(
name: '/profile/:user',
page: () => UserProfile(),
),
Get.toNamed("/profile/34954"); // Bilgi gönderimi
print(Get.parameters['user']); // Bilgi çekilmesi. Sonuç 34954
var parameters = <String, String>{"flag": "true","country": "italy",};
Get.toNamed("/profile/34954", parameters: parameters); // Bilgi gönderimi
// Çoklu bilgi çekilmesi
print(Get.parameters['user']);
print(Get.parameters['flag']);
print(Get.parameters['country']);

Back metodu ile kolayca bir sayfadan geri dönebilirsiniz.

Get.back();

Eğer bir sayfaya gitmek ve geri dönüş yapılmasını engellemek istiyorsanız getOff kullanabilirsiniz, eğer önceki sayfaları tümüyle kapatmak isterseniz getOffAll kullanabilirsiniz.

Get.off(NextScreen());
Get.offAll(NextScreen());

İsterseniz hazır diyalog bile çıkartabilirsiniz;

Get.defaultDialog(
onConfirm: () => { print("Thanks!"), Get.back()
},
middleText: "Welcome!"
);

Diyalog gibi snackbar, botomSheet, modal açılış vs. birçok imkana daha sahipsiniz. Detaylar için şu linki inceleyebilirsiniz;

https://github.com/jonataslaw/getx/blob/master/documentation/en_US/route_management.md#navigation-with-named-routes

- Dependency Management

GetX ile bağımlılıkları kolayca yönetebilirsiniz. GetX’de Controller isimli nesneler vardır, bu nesneler bi nevi ViewModel gibi düşünebileceğiniz ve projenizdeki business işlerini toplayabileceğiniz bir katmandır. İster controller’ları tek tek istediğiniz gibi şu şekilde ekleyip kullanabilirsiniz;

SplashViewModel controller = Get.put( SplashViewModel() );@override
Widget build(BuildContext context) {
controller.test();
...

İsterseniz GetView’da generic’te tanımlayarak GetPages’in bindinglerini kullanarak şu şekilde kullanabilirsiniz;

GetView<HomeController> ve HomeViewModel

class HomeView extends GetView<HomeViewModel> {
...
class HomeViewModel extends GetxController {
...

HomeViewModel aslında GetX karşılığı HomeController şeklinde olmalıydı. Lakin makalemiz MVVM yapısı oluşturmaya yönelik olduğundan isimlendirmede ViewModel ifadesi kullanılmıştır.

App classınız içindeki GetMaterialApp içindeki pages’de tanımlanan pages;

...
GetPage(
name: AppRoutes.home,
page: () => HomeView(),
binding: HomeBinding()
),
...

Binding kodu;

class HomeBinding implements Bindings {
@override
void dependencies() {
Get.lazyPut<HomeViewModel>(() => HomeViewModel());
}
}

- Utils (Araçlar)

GetX ile çok faydalı pek çok ek araca da sahip olmaktasınız. Şuanki rota bilgisini elde etmekten tutun, platform bilgisi almaya, sayfanın çağrıldığı bilgisini elde etmeye, sayfanın redirect bilgisini elde etmeye, dili öğrenmeye, dil çevirisi yapmaya, çeviriciler kullanamaya ve pek çok ek fayda getiren araçlara varan geniş bir yelpazeye sahiptir.

Çok sayıda araç olduğu için bu makalede araçlarla ilgili detaya girilmeyecektir. Ancak hali hazırda bazılarına değinilmiştir. Aşağıda Localization meselesi ile alakalı bir örnekle Internationalization konusuna değineceğim.

App classında GetMaterialApp içinde translations tanımladım.

class MyApp extends StatelessWidget {

@override
Widget build(BuildContext context) {
return GetMaterialApp(
...
translations: BaseTranslations(),
...

Aşağıda localization için kullanacağım Translation class kodum mevcut. hello keyi basit bir text bassa da message key’inin value’sinin parametre alabildiğini görebilirsiniz.

class BaseTranslations extends Translations {
@override
Map<String, Map<String, String>> get keys => {
'en_US': {
'hello': 'Hello!',
'message': 'Hi @name welcome. Today is @date',
},
'tr_TR': {
'hello': 'Merhaba!',
'message': 'Selam @name hoş geldin. Bugünün tarihi @date',
}
};
}

Ekrana basmak için tr ve trParams ifadelerini kullanıyoruz. trParams ile aşağıdaki gibi karşılık gelen değere parametrelerin gönderilmesi ve ekrana basılması sağlanmaktadır.

Text("hello".tr),
Text('message'.trParams({
'name': 'Gokhan',
'date': DateTime.now().toString()
})),

Görüleceği üzere çok pratik bir şekilde localization işimizi GetX ile yapabilmekteyiz.

GetX ile Projede Observable Kullanımı

Aslında bu konu State Management konusuna ait olsa da ayırmak istedim. GetX ile çok kolay Observable yapı kurabilirsiniz ve nesnelerini de çok pratik bir şekilde kullanabilirsiniz.

Değişken kullanımında GetX size 3 farklı pratik yol sunuyor. 1. yol;

final name = RxString('');
final isLogged = RxBool(false);
final count = RxInt(0);
final balance = RxDouble(0.0);
final items = RxList<String>([]);
final myMap = RxMap<String, int>({});
// 1. yolda custom kullanıma imkanınız bulunmamaktadır.

2. yol generic yapıları kullanmak;

final name = Rx<String>('');
final isLogged = Rx<Bool>(false);
final count = Rx<Int>(0);
final balance = Rx<Double>(0.0);
final number = Rx<Num>(0);
final items = Rx<List<String>>([]);
final myMap = Rx<Map<String, int>>({});
final user = Rx<User>(); // Custom class kullanımı

3. yol obs kullanmak;

final name = ''.obs;
final isLogged = false.obs;
final count = 0.obs;
final balance = 0.0.obs;
final number = 0.obs;
final items = <String>[].obs;
final myMap = <String, int>{}.obs;
final user = User().obs; // Custom class kullanımı

Gördüğünüz gibi kullanım çok çok basite indirgenmiş durumdadır.

Kullanım örneğide şu şekildedir. Counter isimli bir nesnemiz olduğunu var sayalım. Bu uygulamanın tümünde ortak kullanılacak bir değişken olsun. Hangi sayfadan değiştirilirse değiştirilsin her sayfada değişikliğin yansımasını sağlayalım. Örneği basitleştirmek adına uygulamaya ait classa değişken koydum.

class MyApp extends StatelessWidget {

static var counter = 0.obs;

Gelelim bu değeri nasıl güncelleyeceğimize. Güncelleme aşağıda görüleceği gibi aşırı derecede basittir. Örnekte değişikliği bir buttona basıçla tetikliyorum..

FlatButton(onPressed: () => { 
MyApp.counter.value++
}, child: Text("+"),),

Bu değişken ana sayfada veya diğer sayfalarda da bir textte gösterimi sağlansın. Bunun içinde UI’da Text Wigdet ekliyor olacağız. Observe edilme ile ekrana değeri göstermek için 3 farklı yol mevcuttur, bu yollar alttaki örneklerle gösterilecektir.

İlk örnekte ekrana değeri basmak için Obx kullandım. Örneklerde MyApp classından counter değişkenine ait değeri basacaktır. Eğer değerde bir değişiklik olursa otomatikman UI’da hemen güncellenecektir. Obx size herhangi bir controller’a bağlı kalmadan, birden çok controller’dan gelebilecek değişiklikleri de yansıtabilme imkanı sağlamaktadır. Daha basit bir yazım tarzı vardır ve kullanımı en basit tiptir. Kaynakların en iyi şekilde tüketilmesine odaklanılmıştır. Ram tüketimi bakımından GetX’den daha ekonomik ancak GetBuilder’dan daha dezavantajlı durumdadır. Eğer bindings kullanılıyorsa Obx kullanılması tavsiye edilmektedir.

Obx(() => Text(
'Obx: ${MyApp.counter}'
),),

İkinci örneğimizde GetX kullandım. GetX’de tıpkı Obx gibi değişiklik olduğu anda değişikliği yansıtacaktır. GetX’de sadece değişiklik gerekli olduğu zaman güncellenme yapılması sağlanmaktadır. Bir sürü değişiklik talebi gelse bile GetX filtreleme yaparak gerekli olanları seçer. Eğer nesneye aynı değer atanırsa değişiklik yapılmayacağını anlar ve güncelleme yaptırmaz. Aynı zamanda bir controller instantiate etmek istemediğinizde de kullanabilirsiniz. GetX zengin reactive imkanları size sunmaktadır, lakin performansı GetBuilder’a göre daha dezavantajlıdır çünkü daha fazla RAM tüketmektedir.

GetX<HomeViewModel>(
builder: (c) =>
Text( 'GetX worked: ${c.getCounter()}')
),

Üçüncü örnekte ise GetBuilder kullandım. GetBuilder en büyük avantajı RAM tüketimi en az olan yöntem olması ile diğerlerinden ayrılır ve en ekonomik yöntemdir. GetBuilder kullanabilmeniz Controller’da (bu makalede ViewModel olarak değerlendiriliyor) için el ile update çağrımı yapmanız gerekmektedir. Manual olarak update çağrımı yapmanız ise dezavantajıdır.

Öncelikle View katmanımızdaki GetBuilder kodunu ekleyelim ve bir adet unique id tanımlayalım.

GetBuilder<HomeViewModel>(
id: 'getBuilderSampleId',
builder: (c) => Text(
'GetBuilder worked: ${c()}',
),
),

Update edilmesi için buttona tıklayınca tetikleyecek şekilde bir kod örneği yazılmıştır.

FlatButton(onPressed: () => { controller.updateSampleWithGetBuilder }, child: Text('Update Sample With GetBuilder'),),

Controllerda update çağrımı yapılması sağlanmalıdır. İsterseniz controller.update([‘getBuilderSampleId’]) şeklinde de direkt çağırabilirsiniz.

void updateSampleWithGetBuilder() {
update(['getBuilderSampleId']);
}

Dördüncü yol ise MixinBuilder. Bu yol en ağır en çok kaynak tüketen yoldur. GetBuilder ile Obx’in karma türüdür. Hem manual update etmek hem de reactive nesnelerde değişim olduğu zaman aynı anda tetiklenmesi istenildiğinde bu yol tercih edilebilir. Bu yöntem ile nesne reactive bir nesne olmasa bile update çağrımıyla ekranda güncelleme yapabilmektesiniz.

MixinBuilder<HomeViewModel>(
id: 'mixinBuilderSampleId',
builder: (c) => Text(
'MixinBuilder worked: ${c.getCounter()} - normalVal: ${c.normalVal}',
),
),

Alttaki kod örneği ile yukardaki kodu tetikleyebilirsiniz;

FlatButton(onPressed: () => {
controller.normalVal++,
controller.update(['mixinBuilderSampleId'])
}, child: Text('Mixin Builder Test'),),

Kapanış

GetX pek çok önemli fayda sağlamaktadır. Her şeyden önce Flutter’da işlerimizi daha kolay ve pratik bir şekilde yapmamızı sağlamaktadır. GetX ile setState yazma dertlerine son verebilirsiniz ve UI’da daha kolay güncelleme yapma imkanına sahip olabilirsiniz.

GetX çok sayıda ek fayda sağlamaktadır bu sebepten tek bir makalede hepsine değinmem mümkün olmadı. Ancak değinmemi istediğiniz ek özellik veya faydaları varsa lütfen yorumlarda iletiniz. Böylece eğer bende konuyu inceleyip uygunsa herkesin faydalanması için makaleyi güncelleyerek ilettiğiniz konuya değinebilirim.

Bu makalenin yazımında desteklerinden dolayı Emre Karataş’a teşekkür ederim. Ek olarak GetX ile demo örnek oluşturan ve benim de incelerken yararlanmamı sağlayan KoçSistem OneFrame ekibine ve demo örneğini yazan Mehmet Can Karabağ’a teşekkür ederim.

Umarım makaleyi beğenir ve faydalı bulursunuz.

Teşekkürler
N. Gökhan Alp

Demo Uygulama Kodu:
https://github.com/devgokhan/FlutterGetXdemo/

Referanslar:

--

--