Ana içeriğe atla

Kod üretimi hakkında

Kod üretimi, bizim yerimize kod üretmesi için bir araç kullanma fikridir. Dart'ta bunun dezavantajı, bir uygulamayı "derlemek" için fazladan bir adım gerektirmesidir. Yine de Dart ekibi bu soruna olası bir çözüm üzerinde çalıştığı için, bu sorun yakın gelecekte ortadan kalkabilir.

Riverpod bağlamında kod üretimi, bir "provider" tanımlamak için kullanılan sözdizimini biraz değiştirmekle ilgilidir. Örneğin şunun yerine:

final fetchUserProvider = FutureProvider.autoDispose.family<User, int>((
ref,
userId,
) async {
final json = await http.get('api/user/$userId');
return User.fromJson(json);
});

Kod üretimi kullanarak şöyle yazardık:


Future<User> fetchUser(Ref ref, {required int userId}) async {
final json = await http.get('api/user/$userId');
return User.fromJson(json);
}

Riverpod kullanırken kod üretimi tamamen isteğe bağlıdır. Riverpod'u kod üretimi olmadan kullanmak da tümüyle mümkündür. Bununla birlikte Riverpod kod üretimini benimser ve kullanılmasını önerir.

Riverpod'un kod üreticisinin nasıl kurulacağı ve kullanılacağı hakkında bilgi için

Başlarken sayfasına bakın. Dokümantasyonun kenar çubuğundan kod üretimini etkinleştirmeyi unutmayın.

Kod üretimi kullanmalı mıyım?

Riverpod'da kod üretimi isteğe bağlıdır. Bunu göz önünde bulundurarak, onu kullanmalı mısınız yoksa kullanmamalı mısınız diye merak ediyor olabilirsiniz.

Yanıt şu: Yalnızca başka şeyler için zaten kod üretimi kullanıyorsanız. (bkz. Freezed, json_serializable vb.)
Dart ekibi "macros" adlı bir özellik üzerinde çalışırken, Riverpod'u kullanmanın önerilen yolu kod üretimiydi. Ne yazık ki bu özellik iptal edildi.

Kod üretimi pek çok fayda sağlasa da şu anda hâlâ oldukça yavaştır. Dart ekibi kod üretiminin performansını iyileştirmek üzere çalışıyor, ancak bunun ne zaman kullanılabilir olacağı ve ne kadar iyileşme sağlayacağı belirsiz. Dolayısıyla, projenizde halihazırda kod üretimi kullanmıyorsanız, yalnızca Riverpod için kod üretimine başlamak muhtemelen zahmete değmez.

Öte yandan pek çok uygulama, Freezed veya json_serializable gibi paketlerle zaten kod üretimi kullanıyor. Bu durumda projeniz muhtemelen kod üretimi için zaten hazırdır ve Riverpod'u kullanmak basit olacaktır.

Kod üretimi kullanmanın faydaları nelerdir?

"Riverpod'da kod üretimi isteğe bağlıysa, neden kullanayım?" diye merak ediyor olabilirsiniz.

Paketlerde her zaman olduğu gibi: Hayatınızı kolaylaştırmak için. Bunlar arasında (bunlarla sınırlı olmamak üzere) şunlar yer alır:

  • Daha iyi sözdizimi; daha okunabilir/esnek ve daha düşük bir öğrenme eğrisi.
    • Provider'ın türü hakkında endişelenmenize gerek yok. Siz mantığınızı yazın, Riverpod sizin için en uygun provider'ı seçsin.
    • Sözdizimi artık "kirli bir global değişken" tanımlıyormuşuz gibi görünmüyor. Bunun yerine özel bir fonksiyon/sınıf tanımlıyoruz.
    • Provider'lara parametre aktarmak artık kısıtlı değil. Family kullanmak ve tek bir konumsal parametre aktarmakla sınırlı kalmak yerine, artık istediğiniz parametreyi aktarabilirsiniz. Buna adlandırılmış parametreler, isteğe bağlı parametreler ve hatta varsayılan değerler de dahildir.
  • Riverpod ile yazılan kodun durumu koruyan hot-reload'u.
  • Hata ayıklayıcının kullandığı ek üstverinin üretilmesi sayesinde daha iyi hata ayıklama.

Sözdizimi

Bir provider tanımlamak:

Kod üretimi kullanarak bir provider tanımlarken şu noktaları akılda tutmak faydalıdır:

  • Provider'lar ya işaretlenmiş (annotated) bir fonksiyon ya da işaretlenmiş bir sınıf olarak tanımlanabilir. İkisi hemen hemen aynıdır, ancak sınıf tabanlı provider'ın avantajı, dış nesnelerin provider'ın durumunu değiştirmesini (yan etkiler) sağlayan public metotlar içerebilmesidir. Fonksiyonel provider'lar, yalnızca bir build metodu olan sınıf tabanlı bir provider yazmanın sözdizimsel kısaltmasıdır ve bu nedenle kullanıcı arayüzü tarafından değiştirilemezler.
  • Dart'ın tüm asenkron ilkelleri (Future, FutureOr ve Stream) desteklenir.
  • Bir fonksiyon asenkron olarak işaretlendiğinde, provider hata/yükleniyor durumlarını otomatik olarak yönetir ve bir AsyncValue sunar.
Fonksiyonel
(Public metotlar kullanarak
yan etki gerçekleştiremez)
Sınıf Tabanlı
(Public metotlar kullanarak
yan etki gerçekleştirebilir)
Senkron

String example(Ref ref) {
return 'foo';
}

class Example extends _$Example {

String build() {
return 'foo';
}

// Add methods to mutate the state
}
Asenkron - Future

Future<String> example(Ref ref) async {
return Future.value('foo');
}

class Example extends _$Example {

Future<String> build() async {
return Future.value('foo');
}

// Add methods to mutate the state
}
Asenkron - Stream

Stream<String> example(Ref ref) async* {
yield 'foo';
}

class Example extends _$Example {

Stream<String> build() async* {
yield 'foo';
}

// Add methods to mutate the state
}

autoDispose'u etkinleştirme/devre dışı bırakma:

Kod üretimi kullanıldığında provider'lar varsayılan olarak autoDispose'dur. Yani kendilerine bağlı bir dinleyici kalmadığında (ref.watch/ref.listen) kendilerini otomatik olarak yok ederler.
Bu varsayılan ayar Riverpod'un felsefesiyle daha uyumludur. Kod üretimi kullanılmayan varyantta, package:provider'dan geçiş yapan kullanıcıları gözetmek için autoDispose başlangıçta varsayılan olarak kapalıydı.

autoDispose'u devre dışı bırakmak isterseniz, bunu anotasyona keepAlive: true aktararak yapabilirsiniz.

// AutoDispose provider (keepAlive is false by default)

String example1(Ref ref) => 'foo';

// Non autoDispose provider
(keepAlive: true)
String example2(Ref ref) => 'foo';

Bir provider'a parametre aktarmak (family):

Kod üretimi kullanıldığında, bir provider'a parametre aktarmak için artık family değiştiricisine ihtiyacımız yok. Bunun yerine provider'ımızın ana fonksiyonu; adlandırılmış, isteğe bağlı veya varsayılan değerli parametreler dahil olmak üzere istediğiniz sayıda parametre alabilir.
Ancak bu parametrelerin yine de tutarlı bir == davranışına sahip olması gerektiğini unutmayın. Yani ya değerler önbelleğe alınmalı ya da parametreler =='i geçersiz kılmalıdır.

FonksiyonelSınıf Tabanlı

String example(
Ref ref,
int param1, {
String param2 = 'foo',
}) {
return 'Hello $param1 & $param2';
}

class Example extends _$Example {

String build(
int param1, {
String param2 = 'foo',
}) {
return 'Hello $param1 & $param2';
}

// Add methods to mutate the state
}

Kod üretimi kullanmayan varyanttan geçiş:

Kod üretimi kullanmayan varyantta, provider'ınızın türünü elle belirlemeniz gerekir. Kod üretimi kullanan varyanta geçerken karşılık gelen seçenekler şunlardır:

Provider
Önce
final exampleProvider = Provider.autoDispose<String>(
(ref) {
return 'foo';
},
);
Sonra

String example(Ref ref) {
return 'foo';
}
NotifierProvider
Önce
final exampleProvider = NotifierProvider.autoDispose<ExampleNotifier, String>(
ExampleNotifier.new,
);

class ExampleNotifier extends Notifier<String> {

String build() {
return 'foo';
}

// Add methods to mutate the state
}
Sonra

class Example extends _$Example {

String build() {
return 'foo';
}

// Add methods to mutate the state
}
FutureProvider
Önce
final exampleProvider = FutureProvider.autoDispose<String>((ref) async {
return Future.value('foo');
});
Sonra

Future<String> example(Ref ref) async {
return Future.value('foo');
}
StreamProvider
Önce
final exampleProvider =
StreamProvider.autoDispose<String>((ref) async* {
yield 'foo';
});
Sonra

Stream<String> example(Ref ref) async* {
yield 'foo';
}
AsyncNotifierProvider
Önce
final exampleProvider =
AsyncNotifierProvider.autoDispose<ExampleNotifier, String>(
ExampleNotifier.new,
);

class ExampleNotifier extends AsyncNotifier<String> {

Future<String> build() async {
return Future.value('foo');
}

// Add methods to mutate the state
}
Sonra

class Example extends _$Example {

Future<String> build() async {
return Future.value('foo');
}

// Add methods to mutate the state
}
StreamNotifierProvider
Önce
final exampleProvider =
StreamNotifierProvider.autoDispose<ExampleNotifier, String>(() {
return ExampleNotifier();
});

class ExampleNotifier extends StreamNotifier<String> {

Stream<String> build() async* {
yield 'foo';
}

// Add methods to mutate the state
}
Sonra

class Example extends _$Example {

Stream<String> build() async* {
yield 'foo';
}

// Add methods to mutate the state
}