Flutter Provider Türleri 💫 🌌 ✨
Gülsen Keskin
Posted on January 29, 2022
Not: Bu yazı Flutter Community tarafından paylaşılan makalenin Türkçe çevirisi niteliğini taşır. Orijinal içeriğe linkten ulaşabilirsiniz.
Flutter'da bir çok Provider türü vardır bunlardan bazıları:
• Provider
• ListenableProvider
• ChangeNotifierProvider
• ValueListenableProvider
• StreamProvider
• FutureProvider
• MultiProvider
• ProxyProvider
• ChangeNotifierProxyProvider
Bu yazıda provider türlerini ve bunlardan hangisinin ne zaman kullanılacağını öğreneceğiz.
Bunun için aşağıdaki yapıyı kullanacağız.
• "Do something" düğmesi, uygulama durumunu değiştiren herhangi bir uygulama olayını temsil eder.
• "Show something" Metin widget'ı, kullanıcı arayüzünün uygulama durumunu görüntülemesi gereken herhangi bir bölümünü temsil eder.
• Soldaki yeşil dikdörtgen ve sağdaki mavi dikdörtgen, widget ağacının iki farklı bölümünü temsil eder. Bir etkinliğin ve güncellediği kullanıcı arayüzünün uygulamanın herhangi bir bölümünde olabileceğini vurgulamak için kullanılmışlardır.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('My App')),
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
color: Colors.green[200],
child: RaisedButton(
child: Text('Do something'),
onPressed: () {},
),
),
Container(
padding: const EdgeInsets.all(35),
color: Colors.blue[200],
child: Text('Show something'),
),
],
),
),
);
}
}
Provider paketini pubspec.yaml dosyanıza ekleyin:
dependencies:
provider: ^6.0.2
Bunu herhangi bir sayfada kullanmak için aşağıdaki şekilde import edebilirsiniz.
import 'package:provider/provider.dart';
Provider
Tahmin edebileceğiniz gibi, Provider, Provider widget türlerinin en temelidir.
Widget ağacında herhangi bir yere bir değer (genellikle bir data model object) sağlamak için kullanabilirsiniz.
Ancak, bu değer değiştiğinde widget ağacını güncellemenize yardımcı olmaz.
class MyModel {
String someValue = 'Hello';
void doSomething() {
someValue = 'Goodbye';
print(someValue);
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Provider<MyModel>( // <--- Provider
create: (context) => MyModel(),
child: MaterialApp(
home: Scaffold(
appBar: AppBar(title: "Text('My App')),"
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
color: Colors.green[200],
child: Consumer<MyModel>( // <--- Consumer
builder: (context, myModel, child) {
return RaisedButton(
child: Text('Do something'),
onPressed: (){
// We have access to the model.
myModel.doSomething();
},
);
},
)
),
Container(
padding: const EdgeInsets.all(35),
color: Colors.blue[200],
child: Consumer<MyModel>( // <--- Consumer
builder: (context, myModel, child) {
return Text(myModel.someValue);
},
),
),
],
),
),
),
);
}
}
class MyModel { // <--- MyModel
String someValue = 'Hello';
void doSomething() {
someValue = 'Goodbye';
print(someValue);
}
}
Bunu çalıştırdığınızda aşağıdaki sonucu alırsınız:
Kullanıcı arayüzü, modelden gelen "Hello" metniyle oluşturuldu.
“Do something” düğmesine basmak, model üzerinde bir olayın gerçekleşmesine neden olacaktır. Ancak, modelin verileri değiştirilmiş olsa bile, Provider
widget aracı sağladığı değerlerdeki değişiklikleri dinlemediği için kullanıcı arayüzü yeniden oluşturulamadı.
ChangeNotifierProvider
Provider
widget bileşeninden farklı olarak, ChangeNotifierProvider
model nesnesindeki değişiklikleri dinler.
Değişiklik olduğunda, Consumer
altındaki tüm widget'ları yeniden oluşturacaktır.
Kodda Provider'ı ChangeNotifierProvider
olarak değiştirin.
Model sınıfının ChangeNotifier'ı
kullanması (veya extend etmesi) gerekir. Bu sizin notifyListeners()
'a erişmenizi sağlar ve notifyListeners()'ı her çağırdığınızda ChangeNotifierProvider
bilgilendirilecek ve Consumer'lar widget'larını yeniden oluşturacaktır.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<MyModel>( // <--- ChangeNotifierProvider
create: (context) => MyModel(),
child: MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('My App')),
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
color: Colors.green[200],
child: Consumer<MyModel>( // <--- Consumer
builder: (context, myModel, child) {
return RaisedButton(
child: Text('Do something'),
onPressed: (){
myModel.doSomething();
},
);
},
)
),
Container(
padding: const EdgeInsets.all(35),
color: Colors.blue[200],
child: Consumer<MyModel>( // <--- Consumer
builder: (context, myModel, child) {
return Text(myModel.someValue);
},
),
),
],
),
),
),
);
}
}
class MyModel with ChangeNotifier { // <--- MyModel
String someValue = 'Hello';
void doSomething() {
someValue = 'Goodbye';
print(someValue);
notifyListeners();
}
}
Şimdi “Do something” düğmesine bastığınızda, metin “Hello” dan “Goodbye” a dönüşür.
Çoğu uygulamada model sınıfınız kendi dosyasında olacak ve ChangeNotifier
'ı kullanmak için flutter/foundation.dart
dosyasını içe aktarmanız gerekecek. Bunun gerçekten hayranı değilim çünkü bu, iş mantığınızın artık framework'e bağımlı olduğu ve framework'ün bir ayrıntı olduğu anlamına gelir.
Consumer widget'ı, notifyListeners() çağrıldığında altındaki tüm pencere öğelerini yeniden oluşturur.
Butonun güncellenmesi gerekmez, bu nedenle bir Consumer kullanmak yerine Provider.of'u kullanabilir ve listen parametresini "false" olarak ayarlayabilirsiniz.
Bu şekilde, değişiklik olduğunda buton yeniden oluşturulmaz.
class MyButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final myModel = Provider.of<MyModel>(context, listen: false);
return RaisedButton(
child: Text('Do something'),
onPressed: () {
myModel.doSomething();
},
);
}
}
FutureProvider
FutureProvider, temel olarak FutureBuilder widget'ının etrafındaki bir sarmalayıcıdır.
Kullanıcı arayüzünde göstermesi için bazı initial dataları veriyorsunuz ve ayrıca provide etmek istediğiniz değerin bir Future'ını sağlıyorsunuz.
FutureProvider, Future'ın ne zaman tamamlandığını dinler ve ardından Consumerları widget'larını yeniden oluşturmaları için bilgilendirir.
Aşağıdaki kodda, kullanıcı arayüzüne bazı initial dataları vermek için boş bir model kullandım.
Ayrıca 3 saniye sonra yeni bir model döndürme işlevi ekledim.
FutureProvider'ın beklediği şey budur.
Temel Provider gibi, FutureProvider da modelin kendisindeki değişiklikleri dinlemez. Aşağıdaki kod 2 saniye sonra “Do something” butonunu yaparak modeli değiştirir. UI üzerinde herhangi bir etkisi yoktur.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FutureProvider<MyModel>( // <--- FutureProvider
initialData: MyModel(someValue: 'default value'),
create: (context) => someAsyncFunctionToGetMyModel(),
child: MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('My App')),
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
color: Colors.green[200],
child: Consumer<MyModel>( // <--- Consumer
builder: (context, myModel, child) {
return RaisedButton(
child: Text('Do something'),
onPressed: (){
myModel.doSomething();
},
);
},
)
),
Container(
padding: const EdgeInsets.all(35),
color: Colors.blue[200],
child: Consumer<MyModel>( // <--- Consumer
builder: (context, myModel, child) {
return Text(myModel.someValue);
},
),
),
],
),
),
),
);
}
}
Future<MyModel> someAsyncFunctionToGetMyModel() async { // <--- async function
await Future.delayed(Duration(seconds: 3));
return MyModel(someValue: 'new data');
}
class MyModel { // <--- MyModel
MyModel({this.someValue});
String someValue = 'Hello';
Future<void> doSomething() async {
await Future.delayed(Duration(seconds: 2));
someValue = 'Goodbye';
print(someValue);
}
}
FutureProvider
, Consumer
'a Future<MyModel>
tamamlandıktan sonra yeniden oluşturmasını söyler.
Uygulamayı başlangıç değerleriyle yeniden oluşturmak için hot restart'a basın.
"Do something" butonuna basmanın, Future tamamlandıktan sonra bile kullanıcı arayüzünü güncellemediğini unutmayın.
Bu tür bir işlevsellik istiyorsanız, son bölümdeki ChangeNotifierProvider'ı kullanın.
FutureProvider için kullanım durumunuz, bir dosyadan veya ağdan bazı verileri okumak olabilir.Ancak bunu bir FutureBuilder ile de yapabilirsiniz.Uzman olmayan görüşüme göre, FutureProvider bir FutureBuilder'dan çok daha kullanışlı değil.Bir sağlayıcıya ihtiyacım olursa, muhtemelen bir ChangeNotifierProvider kullanırdım ve bir sağlayıcıya ihtiyacım yoksa, muhtemelen bir FutureBuilder kullanırdım.
Yine de bir yorum eklemek isterseniz bunu güncellemekten memnuniyet duyarım.
StreamProvider
StreamProvider, temel olarak StreamBuilder'ın etrafındaki bir sarıcıdır. Bir akış sağlarsınız ve ardından akışta bir olay olduğunda Consumer'lar yeniden oluşturulur. Kurulum, yukarıdaki FutureProvider'a çok benzer.
StreamProvider modelin kendisindeki değişiklikleri dinlemez.
Yalnızca akıştaki yeni eventleri dinler.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StreamProvider<MyModel>( // <--- StreamProvider
initialData: MyModel(someValue: 'default value'),
create: (context) => getStreamOfMyModel(),
child: MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('My App')),
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
color: Colors.green[200],
child: Consumer<MyModel>( // <--- Consumer
builder: (context, myModel, child) {
return RaisedButton(
child: Text('Do something'),
onPressed: (){
myModel.doSomething();
},
);
},
)
),
Container(
padding: const EdgeInsets.all(35),
color: Colors.blue[200],
child: Consumer<MyModel>( // <--- Consumer
builder: (context, myModel, child) {
return Text(myModel.someValue);
},
),
),
],
),
),
),
);
}
}
Stream<MyModel> getStreamOfMyModel() { // <--- Stream
return Stream<MyModel>.periodic(Duration(seconds: 1),
(x) => MyModel(someValue: '$x'))
.take(10);
}
class MyModel { // <--- MyModel
MyModel({this.someValue});
String someValue = 'Hello';
void doSomething() {
someValue = 'Goodbye';
print(someValue);
}
}
StreamProvider, Consumer'a yeni bir stream eventi (akış olayı) olduğunda yeniden oluşturmasını söyler.
Uygulamayı başlangıç değerleriyle yeniden oluşturmak için hot restart a basın.
"Do something" düğmesine basmanın kullanıcı arayüzünü güncellemediğini unutmayın. Bu tür bir işlevsellik istiyorsanız, sadece ChangeNotifierProvider kullanın. Aslında, model nesnenizde bir akış olabilir ve sadece notifyListeners()'ı çağırabilirsiniz.
Bu durumda bir StreamProvider'a hiç ihtiyacınız olmaz.
ValueListenableProvider
ValueListenableProvider'ı immutable state model nesnesi ile kullanabilirsiniz ve değişmezliğin (immutability) bazı avantajları vardır.
class MyModel {
ValueNotifier<String> someValue = ValueNotifier('Hello');
void doSomething() {
someValue.value = 'Goodbye';
}
}
ValueListenableProvider ile içindeki değişiklikleri dinleyebilirsiniz. Ancak, kullanıcı arayüzünden model üzerinde bir method çağırmak istiyorsanız, modeli de sağlamanız gerekir.
Bu nedenle, aşağıdaki kodda bir Provider'ın, MyModel'deki ValueNotifier'ı ValueListenableProvider'a veren bir Consumer'a MyModel sağladığını görebilirsiniz.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Provider<MyModel>(// <--- Provider
create: (context) => MyModel(),
child: Consumer<MyModel>( // <--- MyModel Consumer
builder: (context, myModel, child) {
return ValueListenableProvider<String>.value( // <--- ValueListenableProvider
value: myModel.someValue,
child: MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('My App')),
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
color: Colors.green[200],
child: Consumer<MyModel>( // <--- Consumer
builder: (context, myModel, child) {
return RaisedButton(
child: Text('Do something'),
onPressed: (){
myModel.doSomething();
},
);
},
)
),
Container(
padding: const EdgeInsets.all(35),
color: Colors.blue[200],
child: Consumer<String>(// <--- String Consumer
builder: (context, myValue, child) {
return Text(myValue);
},
),
),
],
),
),
),
);
}),
);
}
}
class MyModel { // <--- MyModel
ValueNotifier<String> someValue = ValueNotifier('Hello'); // <--- ValueNotifier
void doSomething() {
someValue.value = 'Goodbye';
print(someValue.value);
}
}
"Do something" butonuna basmak, ValueListenableProvider sayesinde "Hello"nun "Goodbye" olarak değişmesini sağlar.
Widget ağacının en üstünde bir Consumer yerine Provider.of<MyModel>(context, listen: false)
kullanmak muhtemelen daha iyi olur. Aksi takdirde, her seferinde tüm ağacı yeniden inşa ederiz.
Provider<MyModel>
, myModel'i hem ValueListenableProvider'a hem de "Do something" düğmesinin kapanmasına verir.
ListenableProvider
Bunu yalnızca kendi özel provider'ınızı oluşturmanız gerekiyorsa kullanırsınız.
MultiProvider
Şimdiye kadar ki örneklerimiz yalnızca bir model nesnesi kullandı.
İkinci tür bir model nesnesi sağlamanız gerekiyorsa, providerları iç içe yerleştirebilirsiniz (yukarıdaki ValueListenableProvider örneğinde yaptığım gibi). Ancak, iç içe kullanım dağınıktır. Bunu yapmanın daha düzgün bir yolu, bir MultiProvider kullanmaktır.
Aşağıdaki örnekte, iki ChangeNotifierProviders ile sağlanan iki farklı model vardır.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider( // <--- MultiProvider
providers: [
ChangeNotifierProvider<MyModel>(create: (context) => MyModel()),
ChangeNotifierProvider<AnotherModel>(create: (context) => AnotherModel()),
],
child: MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('My App')),
body: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
color: Colors.green[200],
child: Consumer<MyModel>( // <--- MyModel Consumer
builder: (context, myModel, child) {
return RaisedButton(
child: Text('Do something'),
onPressed: (){
// We have access to the model.
myModel.doSomething();
},
);
},
)
),
Container(
padding: const EdgeInsets.all(35),
color: Colors.blue[200],
child: Consumer<MyModel>( // <--- MyModel Consumer
builder: (context, myModel, child) {
return Text(myModel.someValue);
},
),
),
],
),
// SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
color: Colors.red[200],
child: Consumer<AnotherModel>( // <--- AnotherModel Consumer
builder: (context, myModel, child) {
return RaisedButton(
child: Text('Do something'),
onPressed: (){
myModel.doSomething();
},
);
},
)
),
Container(
padding: const EdgeInsets.all(35),
color: Colors.yellow[200],
child: Consumer<AnotherModel>( // <--- AnotherModel Consumer
builder: (context, anotherModel, child) {
return Text('${anotherModel.someValue}');
},
),
),
],
),
],
),
),
),
);
}
}
class MyModel with ChangeNotifier { // <--- MyModel
String someValue = 'Hello';
void doSomething() {
someValue = 'Goodbye';
print(someValue);
notifyListeners();
}
}
class AnotherModel with ChangeNotifier { // <--- AnotherModel
int someValue = 0;
void doSomething() {
someValue = 5;
print(someValue);
notifyListeners();
}
}
İlk "Do something" düğmesine basmak, "Hello"yu "Goodbye" olarak değiştirecektir. İkinci “Do something” düğmesine basmak “0”ı “5” olarak değiştirecektir.
Bununla tek ChangeNotifierProvider arasında pek bir fark yoktur.
Farklı Consumer'ların doğru modeli alma yolu, gösterdikleri türe göredir. Yani, Consumer<MyModel>
, MyModel'i
alır ve Consumer<AnotherModel>
, AnotherModel'i
alır.
ProxyProvider
Ya sağlamak istediğiniz iki modeliniz varsa, ancak modellerden biri diğerine bağlıysa?
Bu durumda bir ProxyProvider kullanabilirsiniz.
Bir ProxyProvider değeri bir providerdan alır ve başka bir provider'a enjekte edilmesini sağlar.
Bir ProxyProvider kurma şekliniz ilk başta kafa karıştırıcı olabilir, bu yüzden bununla ilgili küçük bir açıklama eklememe izin verin.
MultiProvider(
providers: [
ChangeNotifierProvider<MyModel>(
create: (context) => MyModel(),
),
ProxyProvider<MyModel, AnotherModel>(
update: (context, myModel, anotherModel) => AnotherModel(myModel),
),
],
Temel ProxyProvider'ın iki türü vardır. Birinci tip, ikinci tipin bağlı olduğu şeydir. Yani, başka bir Provider tarafından zaten sağlanmış olan bir modeldir. Güncelleme kapanışında ikinci model tipine enjekte edilir. Üçüncü parametre (anotherModel) önceki yerleşik değeri saklar, ancak bunu burada kullanmıyoruz. Sadece myModel'i AnotherModel'in yapıcısına iletiyoruz.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider( // <--- MultiProvider
providers: [
ChangeNotifierProvider<MyModel>( // <--- ChangeNotifierProvider
create: (context) => MyModel(),
),
ProxyProvider<MyModel, AnotherModel>( // <--- ProxyProvider
update: (context, myModel, anotherModel) => AnotherModel(myModel),
),
],
child: MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('My App')),
body: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
padding: const EdgeInsets.all(20),
color: Colors.green[200],
child: Consumer<MyModel>( // <--- MyModel Consumer
builder: (context, myModel, child) {
return RaisedButton(
child: Text('Do something'),
onPressed: (){
myModel.doSomething('Goodbye');
},
);
},
)
),
Container(
padding: const EdgeInsets.all(35),
color: Colors.blue[200],
child: Consumer<MyModel>( // <--- MyModel Consumer
builder: (context, myModel, child) {
return Text(myModel.someValue);
},
),
),
],
),
Container(
padding: const EdgeInsets.all(20),
color: Colors.red[200],
child: Consumer<AnotherModel>( // <--- AnotherModel Consumer
builder: (context, anotherModel, child) {
return RaisedButton(
child: Text('Do something else'),
onPressed: (){
anotherModel.doSomethingElse();
},
);
},
)
),
],
),
),
),
);
}
}
class MyModel with ChangeNotifier { // <--- MyModel
String someValue = 'Hello';
void doSomething(String value) {
someValue = value;
print(someValue);
notifyListeners();
}
}
class AnotherModel { // <--- AnotherModel
MyModel _myModel;
AnotherModel(this._myModel);
void doSomethingElse() {
_myModel.doSomething('See you later');
print('doing something else');
}
}
Metin “Hello” ile başlar.
“Do something” düğmesine bastığınızda, MyModel metni “Goodbye” olarak değiştirir. MyModel dinleyicisini (ChangeNotifierProvider) bilgilendirir ve kullanıcı arayüzü yeni metinle yeniden oluşturulur.
"Do something else" düğmesine bastığınızda, AnotherModel MyModel'i (ProxyProvider tarafından enjekte edilmiştir) alır ve metnini "See you later" olarak değiştirir. MyModel dinleyicilerine değişiklikleri bildirdiği için, kullanıcı arayüzü tekrar güncellenir. AnotherModel'in değişen kendi verileri olsaydı, ProxyProvider değişiklikleri dinlemediğinden UI güncellenmezdi. Bunun için bir ChangeNotifierProxyProvider gerekir.
Provider builder ve value constructors
Bitirmeden önce, Provider'ı kullanmayı öğrenirken kafamı karıştıran bir şeyi daha açıklamak istiyorum.
Provider widget öğelerinin çoğu (tümü değilse de) iki tür constructor'a sahiptir. basic constructor, model nesnenizi oluşturduğunuz bir oluşturma işlevi alır. Bunu yukarıdaki örneklerin çoğunda yaptık.
Provider<MyModel>(
create: (context) => MyModel(),
child: ...
)
MyModel Nesnenin create fonksiyonunda oluşturulduğunu görebilirsiniz .
Nesneniz zaten oluşturulduysa ve yalnızca ona bir referans sağlamak istiyorsanız, value adlı named (adlandırılmış) constructor'ı kullanabilirsiniz:
final myModel = MyModel();
...
Provider<MyModel>.value(
value: myModel,
child: ...
)
Burada MyModel daha önce oluşturuldu ve sadece referans olarak iletildi. Ağdan veri yüklemek için modelde bir yöntemi çağırabilmeniz için modelinizi initState() yönteminde başlatmış olsaydınız bunu yapardınız.
Posted on January 29, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.