English | French | Português | 简体中文 | Español | 한국어 | বাংলা | 日本語 | Turkish | Italian
InheritedWidget'ı sarmalayarak kullanımını kolaylaştırır ve daha fazla yeniden kullanılabilir hale getirir.
Elle InheritedWidget yazmak yerine provider
kullanarak şunları elde edersiniz:
- kaynakların(resources) basitleştirilmiş tahsisi/tasfiyesi
- lazy-load eklentisini
- her seferinde yeni bir sınıf oluşturmaya kıyasla büyük ölçüde azaltılmış bir boilerplate
- devtool dostu - Provider kullanarak, uygulamanızın durumu Flutter devtool'da görünür olacaktır
- InheritedWidgetları kullanmanın daha esnek bir yolu olarak yeni widgetlar(Bkz. Provider.of/Consumer/Selector)
- katlanarak büyüyen bir dinleme mekanizmasına sahip sınıflar için artırılmış ölçeklenebilirlik karmaşıklıktadır (bildirim gönderimi için O(N) olan ChangeNotifier gibi).
provider
hakkında daha detaylı bilgi için, dokümanı inceleyebilirsiniz documentation.
Ayrıca:
- Resmi Flutter state management belgeleri,
provider
+ ChangeNotifier'ın nasıl kullanılacağını göstermektedir. provider
+ ChangeNotifier kullanılarak geliştirilmiş bir uygulama örneği Flutter state management mimarisi örneği- State management mimarilerinde bir
provider
kullanan flutter_bloc ve Mobx
-
Hem
FutureProvider
hem deStreamProvider
içininitialData
artık gerekli.Geçiş işlemleri için:
FutureProvider<int>( create: (context) => Future.value(42), child: MyApp(), ) Widget build(BuildContext context) { final value = context.watch<int>(); return Text('$value'); }
yenilenmiş hali:
FutureProvider<int?>( initialValue: null, create: (context) => Future.value(42), child: MyApp(), ) Widget build(BuildContext context) { // ? ile işaretlendiğine emin ol watch<int?> final value = context.watch<int?>(); return Text('$value'); }
-
ValueListenableProvider
kaldırıldıGeçiş yapmak için bunun yerine
ValueListenableBuilder
ile birlikteProvider
kullanabilirsiniz:ValueListenableBuilder<int>( valueListenable: myValueListenable, builder: (context, value, _) { return Provider<int>.value( value: value, child: MyApp(), ); } )
Provider'lar yalnızca bir değeri expose etmenizi değil, aynı zamanda onu oluşturmanıza, dinlemenize ve atmanıza da olanak tanır.
Yeni oluşturulmuş bir objeyi expose etmek için, bir providerın varsayılan constructer'ını kullanın.
Bir nesneyi create etmek istiyorsanız .value
yapıcısını kullanmayın
aksi takdirde istenmeyen yan etkiler oluşacaktır.
Bakınız StackOverflow yanıtı
Bu da değer oluşturmak için .value
constructor'ı kullanılmasının neden istenmediğini açıklıyor.
- Yapmanız Gereken
create
methodunun altında yeni bir obje oluşturmak.
Provider(
create: (_) => MyModel(),
child: ...
)
- Yapmayın!
Provider.value
ile obje oluşturmak.
ChangeNotifierProvider.value(
value: MyModel(),
child: ...
)
-
Yapmayın! objenizi zaman içerisinde değişebilecek değişkenler üzerine kurmak.
Böyle bir durumda, oluşturduğunuz obje değiştiğinde veriler asla update edilmeyecektir.
int count;
Provider(
create: (_) => MyModel(count),
child: ...
)
Eğer atadığınız obje zaman içerisinde değişecekse,
ProxyProvider
kullanmayı tercih edin:
int count;
ProxyProvider0(
update: (_, __) => MyModel(count),
child: ...
)
DIPNOT:
Provider'ın sağladığı create
/update
callback methodlarını kullanırken, kullandığınız bu callbacklerin
default olarak lazily tanımlamasına uyduğunu unutmayın.
Bu şu anlama geliyor, create
/update
methodları en az bir kez çağırılana kadar asla dinlenmeyecekler.
Bu durumu değiştirerek, veriler üzerinde işlem yapmak isterseniz, lazy
parametresini kullanabilirsiniz.
MyProvider(
create: (_) => Something(),
lazy: false,
)
Zaten halihazırda bir obje instance'ına sahipseniz ve onu tekrar kullanmak istiyorsanız, .value
constructor'ı aradığınız şey olacaktır.
Yanlış kullanıldığı durumda, zaten kullanımda olan objeniz için dispose
method'ı devreye girebilir.
- Yapmanız Gereken ChangeNotifier içerisinde bulunan veriyi
ChangeNotifierProvider.value
ile çağırın.
MyChangeNotifier variable;
ChangeNotifierProvider.value(
value: variable,
child: ...
)
- Yapmayın! ChangeNotifier ile halihazırda çağırılmış veriyi kullanmak.
MyChangeNotifier variable;
ChangeNotifierProvider(
create: (_) => variable,
child: ...
)
Bir veriyi dinlemenin en basit yolu [BuildContext] üzerinde bulunan extension methodları aracılığı ile veriye erişmektir.
context.watch<T>()
,T
üzerindeki widget değişimleri dinler.context.read<T>()
,T
okuması yapar fakat dinleme işlemi yapmaz.context.select<T, R>(R cb(T value))
,T
verisinin sadece belli bir kısmı için dinleme işlemi yapar.
Benzer şekilde davranacak olan Provider.of<T>(context)
static methodu da dinleme işlemi için
kullanılabilir. listen
parametresi false
olarak belirlendiğinde (Provider.of<T>(context, listen: false)
'da olduğu gibi), aynı
read
işlemi yaptığımız durumdaki gibi davranacaktır.
context.read<T>()
işleminin widget rebuild işlemini triggerlamayacak olduğunu not
ediniz. ayrıca StatelessWidget.build
/State.build
içerisinde de çağırılamaz.
Fakat, bu methodların dışında istenilen şekilde kullanılabilir.
Bu methodların her biri ilişkili olduğu widget ağacının başlangıcında bulunan
BuildContext
methoduna dönecek ve en yakın T
verisi ile eşlenecektir.
(eğer eşlenecek bir veri bulamazsa boşa düşecek).
Bu işlem bir O(1) işlemi gibidir. Widgetın bulunduğu ağaçta bir şey içermez.
İlk örnekle birleştirdiğmiz durumda veri dinleme işlemleri, String
verisi expose edilerek okunacak ve "Hello World" render edilecektir.
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
// "watch" işlemi ile elde etmek istediğiniz objenin tipini belirtmeyi unutmayın!
context.watch<String>(),
);
}
}
Alternatif olarak, bu yöntemleri kullanmak yerine, Consumer veya Selector kullanılabilir.
Bu yöntemler performans açısından yararlı olabilir, ayrıca BuildContext
elde etmek
zor olduğunda da kullanılabilir.
Ayrıca daha fazla bilgi FAQ, Consumer dokümanı ve Selector dokümanına bakabilirsiniz.
Bazen, providerın mevcut olmadığı durumları desteklemek isteyebiliriz. Örneğin, oluşturduğumuz bir widget projenin farklı noktalarında tekrar kullanılabilir, ve bu durum providerın dışında gerçekleşebilir.
Böyle durumlarda, context.watch
/context.read
yöntemini kullanarak, generic bir
type'ı null olmaktan kurtarabiliriz. Öyle ki:
context.watch<Model>()
bu durum eğer matchlenebileceği bir provider bulamaz ise ProviderNotFoundException
fırlatacaktır, fakat:
context.watch<Model?>()
match olabileceği bir provider arayacaktır. Eğer bulamazsa bu sefer null
olarak
dönecektir.
Büyük projelerde çok fazla değeri projeye entegre etmeniz gerektiğinde
oluşturduğunuz Provider
'ları iç içe kullanmak yerine:
Provider<Something>(
create: (_) => Something(),
child: Provider<SomethingElse>(
create: (_) => SomethingElse(),
child: Provider<AnotherThing>(
create: (_) => AnotherThing(),
child: someWidget,
),
),
),
Bu şekilde kullanın:
MultiProvider(
providers: [
Provider<Something>(create: (_) => Something()),
Provider<SomethingElse>(create: (_) => SomethingElse()),
Provider<AnotherThing>(create: (_) => AnotherThing()),
],
child: someWidget,
)
Aslında iki yöntem de aynı işlevi yerine getiriyor.MultiProvider
yalnızca
yazdığınız kodun okunabilirliğine katkı sağlıyor.
3.0.0 sürümü ile birlikte, yeni bir provider türü eklendi: ProxyProvider
.
ProxyProvider
farklı provider verilerini birleştirerek yenilerini üretmenize olanak sağlayan bir Provider
türü.
Yeni oluşturulan obje, providerlarınızdan herhangi birinde gerçekleşen bir update'i dinleyecek ve update oluşması durumunda otomatik olarak güncellenecek.
Aşağıdaki örnek ProxyProvider
başka bir providerda oluşan counter dinlemesi ile objenin build edilmesini gösteriyor.
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter()),
ProxyProvider<Counter, Translations>(
update: (_, counter, __) => Translations(counter.value),
),
],
child: Foo(),
);
}
class Translations {
const Translations(this._value);
final int _value;
String get title => 'You clicked $_value times';
}
Birden fazla varyasonu destekliyor, örneğin:
-
ProxyProvider
vsProxyProvider2
vsProxyProvider3
, ...ProxyProvider
için çalışma sırası üstünlüğü numaralandırıldığı konuma bağlı olarak yönetilir. -
ProxyProvider
vsChangeNotifierProxyProvider
vsListenableProxyProvider
, ...Hepsi aynı şekilde çalışır, ancak bir
Provider
'a result yollamak yerine,ChangeNotifierProxyProvider
resultlarıChangeNotifierProvider
'a yollar.
Flutter anlık olarak widget bilgilerini görebileceğiniz devtool ile gelir.
Providerlar temelde birer widget oldukları için, devtool üzerinde onları da inceleyebilirsiniz.
Görselde providera tıklarsanız, expose edilen valueları görebilirsiniz.
(devtool'da alınan screenshotlar example
klasörünü kullanmakta)
Buna sebep olan durum, devtool'un default olarak toString
kullanması ve "Instance of MyClass" dönmesidir.
Daha faydalı olması açısından, iki çözüm bulunur:
-
Flutter tarafından sağlanan Diagnosticable API kullanın.
Bir çok senaryo için, objeler için DiagnosticableTreeMixin, ve custom bir şekilde entegre edilmiş debugFillProperties sorunu çözebilir.
class MyClass with DiagnosticableTreeMixin { MyClass({this.a, this.b}); final int a; final String b; @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); // list all the properties of your class here. // See the documentation of debugFillProperties for more information. properties.add(IntProperty('a', a)); properties.add(StringProperty('b', b)); } }
-
toString
methodunu geçersiz kılın.DiagnosticableTreeMixin'ı (class içerisindeki bir package sorun çıkartıyorsa) kullanamıyorsanız,
toString
'i methodunu geçersiz kılmayı deneyebilirsiniz.Bu yöntem DiagnosticableTreeMixin kullanmaktan daha basittir fakat daha güçsüzdür. Bu yöntemle objeleri detay açısından expand/collapse yapamazsınız.
class MyClass with DiagnosticableTreeMixin { MyClass({this.a, this.b}); final int a; final String b; @override String toString() { return '$runtimeType(a: $a, b: $b)'; } }
Bu hatanın oluşmasının sebebi bir providerı life-cycle açısından bir daha kullanılmayacak bir method içerisinde dinlemeye çalışmanızdan dolayıdır.
Bu durumda ya başka bir life-cycle (build
) methodu kullanın veya güncelleme
durumlarını dinlemekten vazgeçin.
Örneğin, bunun yerine:
initState() {
super.initState();
print(context.watch<Foo>().value);
}
bunu kullanabilirsiniz:
Value value;
Widget build(BuildContext context) {
final value = context.watch<Foo>().value;
if (value != this.value) {
this.value = value;
print(value);
}
}
bu şekilde value
değiştiğinde terminale çıktı alacaksınız.(sadece değiştiğinde)
Alternatif olarak, bunu kullanabilirsiniz:
initState() {
super.initState();
print(context.read<Foo>().value);
}
Bu yöntem value
bir kez print edecek ve sonraki updateleri görmezden gelecektir.
Provide edilmiş objenin entegre edilmesini ReassembleHandler
yapın:
class Example extends ChangeNotifier implements ReassembleHandler {
@override
void reassemble() {
print('Did hot-reload');
}
}
Sonrasında ise tipik provider
yapısını kullanın.
ChangeNotifierProvider(create: (_) => Example()),
ChangeNotifier kullanıyorum, veri update ettiğimde hata alıyorum. Bu neden oluyor?
Bu genellikle ChangeNotifier'ın içerdiği öğelerin çağırıldığı widget ağacının build edildiği sırada değiştirilmesinden dolayı olur.
Yaygın bir senaryo olarak ele alırsak, notifier içerisinde store edilmiş bir future'ı başlattığınız sırada oluşması beklenebilir:
initState() {
super.initState();
context.read<MyNotifier>().fetchSomething();
}
Bu yöntemin kullanılamamasının sebebi state update durumunun senkron bir işlem olmasıdır.
Bu şu anlama geliyor. Bazı widgetlar mutasyon öncesinde build edilir. (önceki değeri alır), bazı widgetlar sonrasında build edilir (yeni değeri alır). Bu durum kullanıcı arayüzünde beklenmeyen problemler yaratabileceği için kullanımına izin verilmez.
Bunun yerine, bu mutasyonu tüm widget ağacını eşit şekilde etkileyebilecek şekilde yapınız:
- direkt olarak modelinizin provider/constructor'ınızın bulunduğu
create
methodu içerisinde:
class MyNotifier with ChangeNotifier {
MyNotifier() {
_fetchSomething();
}
Future<void> _fetchSomething() async {}
}
Bu yöntem "ekstra parametlerin" olmadığı durumlarda faydalı olabilir.
- frame sonunda asenkron bir şekilde:
Bu çok ideal bir yöntem değildir, ama parametreleri mutasyon içerisinde kullanabilmenizi sağlar.
initState() { super.initState(); Future.microtask(() => context.read<MyNotifier>().fetchSomething(someValue); ); }
ChangeNotifier'ı complex state durumlarında kullanmalı mıyım?
Hayır.
Bu durumda nesneleri state olarak tanımlayabilirsiniz. Örneğin, farklı bir yapı
olarak Provider.value()
ve StatefulWidget
kombinasyonunu kullanabilirsiniz.
Burada bir sayaç örneği bulabilirsiniz:
class Example extends StatefulWidget {
const Example({Key key, this.child}) : super(key: key);
final Widget child;
@override
ExampleState createState() => ExampleState();
}
class ExampleState extends State<Example> {
int _count;
void increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Provider.value(
value: _count,
child: Provider.value(
value: this,
child: widget.child,
),
);
}
}
state'i dinlemek için:
return Text(context.watch<int>().toString());
state üzerinde işlem yapmak için:
return FloatingActionButton(
onPressed: () => context.read<ExampleState>().increment(),
child: Icon(Icons.plus_one),
);
Alternatif olarak kendi provider yapınızı da kurabilirsiniz.
Evet. provider
aslında tüm küçük componentleri kullanarak hazırlanmış bir tam kapsayıcı providerdır diyebiliriz.
Neleri içerir:
-
MultiProvider
ile çalışması içinSingleChildStatelessWidget
widgetları kurmak. Bu burada bulunan yapıyı kullanırpackage:provider/single_child_widget
-
context.watch
işlemi yaparak klasikInheritedWidget
elde etmenizi sağlayan InheritedProvider.
Burada ValueNotifier
'ı state olarak kullanan bir örnek bulabilirsiniz:
https://gist.github.com/rrousselGit/4910f3125e41600df3c2577e26967c91
context.watch
kullanmak yerine, dinleme işlemlerini daha spesifik ve belirli objeler üzerinde yapmak için context.select
kullanabilirsiniz.
Örneğin, yazma işlemi yaparken:
Widget build(BuildContext context) {
final person = context.watch<Person>();
return Text(person.name);
}
Bu işlem name
değeri dışında bir değişiklik olursa da widgetın rebuild olmasına neden olacaktır.
Fakat, name
dinleme işlemini context.select
ile yapmak sadece onun dinlenmesini sağlayacaktır.
Widget build(BuildContext context) {
final name = context.select((Person p) => p.name);
return Text(name);
}
Bu yöntem sayesinde, name
dışında başka değerlerde oluşan değişiklikler rebuild triggerlamayacaktır.
Benzer olarak, Consumer/Selector da kullanılabilir. Bu widgetlarda opsiyonel olarak sunulan child
argumenti widget ağacının geri kalanı için rebuild işlemini azaltmak için kullanılabilir:
Foo(
child: Consumer<A>(
builder: (_, a, child) {
return Bar(a: a, child: child);
},
child: Baz(),
),
)
Bu örnekte, A
update edildiğinde sadeceBar
rebuild edilecektir.
Foo
ve Baz
gereksiz şekilde rebuild olmayacaktır.
Hayır. Evet aynı anda farklı providerlar aracılığı ile veri paylaşımı yapabilirsiniz fakat böyle bir durumda widget en yakın olduğu veriyi kullanacaktır.
Bunun yerine, iki providera da daha belirgin tanımlamalar yapmak yardımcı olacaktır.
Örneğin, bu örnek yerine:
Provider<String>(
create: (_) => 'England',
child: Provider<String>(
create: (_) => 'London',
child: ...,
),
),
Bunu tercih edin:
Provider<Country>(
create: (_) => Country('England'),
child: Provider<City>(
create: (_) => City('London'),
child: ...,
),
),
Evet, bir entegrasyon oluşturmak için create
içerisinde bir implementasyon sağlanmalıdır.
abstract class ProviderInterface with ChangeNotifier {
...
}
class ProviderImplementation with ChangeNotifier implements ProviderInterface {
...
}
class Foo extends StatelessWidget {
@override
build(context) {
final provider = Provider.of<ProviderInterface>(context);
return ...
}
}
ChangeNotifierProvider<ProviderInterface>(
create: (_) => ProviderImplementation(),
child: Foo(),
),
provider
farklı obje tiplerdeki objeler için farklı "provider"lar sağlar.
Mevcut tüm obje listesi için here
name | description |
---|---|
Provider | Provider'ın en basit hali. Bir değeri alır ve onu expose eder. |
ListenableProvider | Dinlenebilir bir obje için spesifik bir provider türü. ListenableProvider objeyi dinlerken aynı zamanda widgetı rebuild edilmesi konusunda uyarma işlemlerini listener çağırıldığında tetikler. |
ChangeNotifierProvider | ListenableProvider'ın ChangeNotifier için özelleştirilmiş hali. Otomatik olarak gerekli durumlarda ChangeNotifier.dispose çağırır. |
ValueListenableProvider | ValueListenable dinlemesi yaparak sadece ValueListenable.value değerini expose edin. |
StreamProvider | Bir stream'i dinler ve son entegre edilmiş değeri expose eder. |
FutureProvider | Future alır ve bağımlılık durumunun tamamlanmasını kontrol eder. |
Çok fazla sayıda providera sahipseniz (150+), bazı cihazlarda StackOverflowError
hatası almanız olasıdır. Bunun nedeni çok fazla sayıda providerın aynı anda rebuild edilmesidir.
Bu durumda yapabileceğiniz birkaç çözüm bulunmaktadır:
-
Eğer application'ınınız bir splash-screen'e sahipse, tüm providerları aynı anda değil de zaman içerisinde mount etmeyi deneyin.
Şu şekilde yapılabilir:
MultiProvider( providers: [ if (step1) ...[ <lots of providers>, ], if (step2) ...[ <some more providers> ] ], )
splash screen animasyonunuz sırasında, bu çözümü de uygulayabilirsiniz:
bool step1 = false; bool step2 = false; @override initState() { super.initState(); Future(() { setState(() => step1 = true); Future(() { setState(() => step2 = true); }); }); }
-
MultiProvider
kullanmayı bırakmayı düşünebilirsiniz.MultiProvider
her provider için bir widget ekler.MultiProvider
kullanmamakStackOverflowError
hatasına ulaşmadan önceki limiti arttırabilir.