Ana içeriğe atla

Provider

Provider, tüm provider'ların en temelidir. Bir değer oluşturur... Hepsi bu.

Provider genellikle şu amaçlarla kullanılır:

  • hesaplamaları önbelleğe almak
  • diğer provider'lara bir değer sunmak (örneğin bir Repository/HttpClient).
  • test etmek veya widget'ların bir değeri geçersiz kılmasına bir yol sunmak.
  • select kullanmadan provider/widget yeniden oluşturulmalarını azaltmak.

Hesaplamaları önbelleğe almak için Provider kullanma

Provider, ref.watch ile birleştiğinde senkron işlemleri önbelleğe almak için güçlü bir araçtır.

Bir örnek, yapılacaklar listesini filtrelemektir. Bir listeyi filtrelemek biraz maliyetli olabileceğinden, uygulamamız her yeniden oluşturulduğunda yapılacaklar listemizi filtrelemek istemeyiz. Bu durumda, filtreleme işlemini bizim için yapması için Provider kullanabiliriz.

Bunun için, uygulamamızın bir yapılacaklar listesini yöneten mevcut bir StateNotifierProvider'a sahip olduğunu varsayalım:


class Todo {
Todo(this.description, this.isCompleted);
final bool isCompleted;
final String description;
}

class TodosNotifier extends StateNotifier<List<Todo>> {
TodosNotifier() : super([]);

void addTodo(Todo todo) {
state = [...state, todo];
}
// TODO: "removeTodo" gibi diğer yöntemleri ekleyin, ...
}

final todosProvider = StateNotifierProvider<TodosNotifier, List<Todo>>((ref) {
return TodosNotifier();
});

Bundan sonra, Provider kullanarak tamamlanmış görevleri gösteren filtrelenmiş görevler listesini sunabiliriz:


final completedTodosProvider = Provider<List<Todo>>((ref) {
// Görevlerin (yapılacaklar) listesini `todosProvider`dan alıyoruz
final todos = ref.watch(todosProvider);

// Yalnızca tamamlanan "tümü"nü döndürürüz
return todos.where((todo) => todo.isCompleted).toList();
});

Bu kodla, kullanıcı arayüzümüz artık completedTodosProvider'ı dinleyerek tamamlanmış görevler listesini gösterebilir:

Consumer(builder: (context, ref, child) {
final completedTodos = ref.watch(completedTodosProvider);
// TODO: görev listesini (todos) bir ListView/GridView/... içinde göster.
});

İlginç olan, liste filtrelemesinin artık önbelleğe alınmış olmasıdır.

Bu, tamamlanmış görevler listesinin yeniden hesaplanmayacağı anlamına gelir, ta ki görevler eklenip/silinip/güncellenene kadar, hatta tamamlanmış görevler listesini birkaç kez okusak bile.

Görevler listesi değiştiğinde önbelleği manuel olarak geçersiz kılmamıza gerek yoktur. Provider, ref.watch sayesinde sonucu ne zaman yeniden hesaplaması gerektiğini otomatik olarak bilebilir.

Widget/provider yeniden oluşturulmalarını Provider kullanarak azaltma Provider'ın benzersiz bir yönü, Provider yeniden hesaplandığında bile (genellikle ref.watch kullanıldığında), dinleyen widget/provider'lar değer değişmedikçe güncellemeyecektir.

Gerçek dünyadan bir örnek, sayfalı bir görünümde önceki/sonraki düğmelerini etkinleştirme/devre dışı bırakma olabilir:

"Geri/İleri" düğmesi

Bu durumda, özellikle "önceki" düğmesine odaklanacağız. Bu düğmenin naif bir uygulaması, mevcut sayfa dizinini alan bir widget olacaktır, ve bu dizin 0'a eşitse, düğmeyi devre dışı bırakırız.

Bu kod şöyle olabilir:


final pageIndexProvider = StateProvider<int>((ref) => 0);

class PreviousButton extends ConsumerWidget {
const PreviousButton({super.key});


Widget build(BuildContext context, WidgetRef ref) {
// İlk sayfada değilse bir önceki buton aktif olur
final canGoToPreviousPage = ref.watch(pageIndexProvider) > 0;

void goToPreviousPage() {
ref.read(pageIndexProvider.notifier).update((state) => state - 1);
}

return ElevatedButton(
onPressed: canGoToPreviousPage ? goToPreviousPage : null,
child: const Text('previous'),
);
}
}

Bu kodun sorunu, mevcut sayfayı her değiştirdiğimizde "önceki" düğmesinin yeniden oluşturulacak olmasıdır. İdeal dünyada, düğmenin yalnızca etkin/devre dışı durumu değiştiğinde yeniden oluşturulmasını isteriz.

Buradaki sorunun kökü, kullanıcının önceki sayfaya gidip gidemeyeceğini doğrudan "önceki" düğmesinden hesaplamamızdır.

Bunu çözmenin bir yolu, bu mantığı widget'tan çıkarıp bir Provider içine yerleştirmektir:


final pageIndexProvider = StateProvider<int>((ref) => 0);

// Kullanıcının önceki sayfaya gidip gidemeyeceğini hesaplayan bir provider
final canGoToPreviousPageProvider = Provider<bool>((ref) {
return ref.watch(pageIndexProvider) > 0;
});

class PreviousButton extends ConsumerWidget {
const PreviousButton({super.key});


Widget build(BuildContext context, WidgetRef ref) {
// Artık yeni Provider'ımızı izliyoruz
// Widget'ımız artık önceki sayfaya gidip gidemeyeceğimizi hesaplamıyor.
final canGoToPreviousPage = ref.watch(canGoToPreviousPageProvider);

void goToPreviousPage() {
ref.read(pageIndexProvider.notifier).update((state) => state - 1);
}

return ElevatedButton(
onPressed: canGoToPreviousPage ? goToPreviousPage : null,
child: const Text('previous'),
);
}
}

Bu küçük yeniden düzenlemeyi yaparak, PreviousButton widget'ımız artık sayfa dizini değiştiğinde Provider sayesinde yeniden oluşturulmayacaktır.

Bundan böyle, sayfa dizini değiştiğinde canGoToPreviousPageProvider yeniden hesaplanacaktır. Ancak provider tarafından sunulan değer değişmezse, PreviousButton yeniden oluşturulmayacaktır.

Bu değişiklik, düğmemizin performansını artırdı ve widget'tan mantığı çıkarmanın ilginç bir faydası oldu.