Ana içeriğe atla

Hook'lar hakkında

Bu sayfa hook'ların ne olduğunu ve Riverpod ile nasıl ilişkili olduklarını açıklar.

"Hook'lar", Riverpod'dan bağımsız ayrı bir paketten gelen yaygın yardımcılardır: flutter_hooks.
flutter_hooks tamamen ayrı bir paket olmasına ve (en azından doğrudan) Riverpod ile bir ilgisi bulunmamasına rağmen, Riverpod ve flutter_hooks'u birlikte kullanmak yaygın bir yaklaşımdır.

Hook'ları kullanmalı mısınız?

Hook'lar güçlü bir araçtır, ancak herkes için uygun değildir.
Riverpod'a yeni başladıysanız, hook'ları kullanmaktan kaçının.

Hook'lar faydalı olsa da Riverpod için gerekli değildir.
Riverpod yüzünden hook'ları kullanmaya başlamamalısınız. Aksine, hook'ları kullanmak istediğiniz için kullanmaya başlamalısınız.

Hook'ları kullanmak bir ödünleşmedir. Sağlam ve yeniden kullanılabilir kod üretmek için harika olabilirler, ancak aynı zamanda öğrenilmesi gereken yeni bir kavramdır ve başlangıçta kafa karıştırıcı olabilirler. Hook'lar Flutter'ın temel kavramlarından biri değildir. Bu nedenle Flutter/Dart içinde yabancı bir öğe gibi hissettirirler.

Hook'lar nedir?

Hook'lar, widget'ların içinde kullanılan fonksiyonlardır. Mantığı daha yeniden kullanılabilir ve birleştirilebilir kılmak için StatefulWidget'lara bir alternatif olarak tasarlanmışlardır.

Hook'lar React kaynaklı bir kavramdır ve flutter_hooks, React uygulamasının Flutter'a bir uyarlamasından ibarettir.
Bu nedenle evet, hook'lar Flutter'da biraz yersiz hissettirebilir. İdeal olarak gelecekte, hook'ların çözdüğü soruna özellikle Flutter için tasarlanmış bir çözümümüz olacaktır.

Riverpod'un provider'ları "global" uygulama durumu içinse, hook'lar yerel widget durumu içindir. Hook'lar tipik olarak TextEditingController, AnimationController gibi durum tutan kullanıcı arayüzü nesneleriyle çalışmak için kullanılır.
Ayrıca "builder" kalıbının yerini de alabilirler; FutureBuilder/TweenAnimatedBuilder gibi widget'ları "iç içe geçme" gerektirmeyen bir alternatifle değiştirerek okunabilirliği çarpıcı biçimde artırırlar.

Genel olarak hook'lar şunlar için faydalıdır:

  • Formlar
  • Animasyonlar
  • Kullanıcı olaylarına tepki vermek
  • vb.

Örnek olarak, bir widget'ın görünmez başlayıp yavaşça belirdiği bir fade-in animasyonunu elle uygulamak için hook'ları kullanabiliriz.

StatefulWidget kullansaydık, kod şöyle görünürdü:

class FadeIn extends StatefulWidget {
const FadeIn({Key? key, required this.child}) : super(key: key);

final Widget child;


State<FadeIn> createState() => _FadeInState();
}

class _FadeInState extends State<FadeIn> with SingleTickerProviderStateMixin {
late final AnimationController animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 2),
);


void initState() {
super.initState();
animationController.forward();
}


void dispose() {
animationController.dispose();
super.dispose();
}


Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animationController,
builder: (context, child) {
return Opacity(
opacity: animationController.value,
child: widget.child,
);
},
);
}
}

Hook'lar kullanıldığında bunun eşdeğeri şöyle olurdu:

class FadeIn extends HookWidget {
const FadeIn({Key? key, required this.child}) : super(key: key);

final Widget child;


Widget build(BuildContext context) {
// Bir AnimationController oluşturun. Controller, widget ağaçtan
// çıkarıldığında otomatik olarak yok edilecektir.
final animationController = useAnimationController(
duration: const Duration(seconds: 2),
);

// useEffect; initState + didUpdateWidget + dispose'un eşdeğeridir.
// useEffect'e aktarılan geri çağırma, hook ilk kez çağrıldığında ve
// ardından ikinci parametre olarak aktarılan liste her değiştiğinde çalıştırılır.
// Burada boş bir const liste aktardığımız için, bu tam olarak `initState`e eşdeğerdir.
useEffect(() {
// widget ilk kez oluşturulduğunda animasyonu başlat.
animationController.forward();
// İstersek burada bir miktar "dispose" mantığı döndürebilirdik
return null;
}, const []);

// Animasyon güncellendiğinde bu widget'ı yeniden oluşturmasını Flutter'a söyle.
// Bu, AnimatedBuilder'ın eşdeğeridir
useAnimation(animationController);

return Opacity(
opacity: animationController.value,
child: child,
);
}
}

Bu kodda dikkat çeken birkaç ilginç nokta var:

  • Bellek sızıntısı yok. Bu kod, widget her yeniden oluşturulduğunda yeni bir AnimationController oluşturmaz ve widget ağaçtan çıkarıldığında controller doğru şekilde serbest bırakılır.

  • Aynı widget içinde hook'ları istediğimiz kadar çok kez kullanabiliriz. Örneğin istersek birden fazla AnimationController oluşturabiliriz:


    Widget build(BuildContext context) {
    final animationController = useAnimationController(
    duration: const Duration(seconds: 2),
    );
    final anotherController = useAnimationController(
    duration: const Duration(seconds: 2),
    );

    ...
    }

    Bu, hiçbir olumsuz sonucu olmadan iki controller oluşturur.

  • İstersek bu mantığı ayrı ve yeniden kullanılabilir bir fonksiyona ayıklayabiliriz:

    double useFadeIn() {
    final animationController = useAnimationController(
    duration: const Duration(seconds: 2),
    );
    useEffect(() {
    animationController.forward();
    return null;
    }, const []);
    useAnimation(animationController);
    return animationController.value;
    }

    Ardından bu fonksiyonu, widget bir HookWidget olduğu sürece widget'larımızın içinde kullanabiliriz:

    class FadeIn extends HookWidget {
    const FadeIn({Key? key, required this.child}) : super(key: key);

    final Widget child;


    Widget build(BuildContext context) {
    final fade = useFadeIn();

    return Opacity(opacity: fade, child: child);
    }
    }

    useFadeIn fonksiyonumuzun FadeIn widget'ımızdan tamamen bağımsız olduğuna dikkat edin.
    İstersek o useFadeIn fonksiyonunu bambaşka bir widget'ta kullanabilirdik ve yine çalışırdı!

Hook'ların kuralları

Hook'ların kendine özgü kısıtlamaları vardır:

  • Yalnızca HookWidget'tan türeyen bir widget'ın build metodu içinde kullanılabilirler:

    İyi:

    class Example extends HookWidget {

    Widget build(BuildContext context) {
    final controller = useAnimationController();
    ...
    }
    }

    Kötü:

    // HookWidget değil
    class Example extends StatelessWidget {

    Widget build(BuildContext context) {
    final controller = useAnimationController();
    ...
    }
    }

    Kötü:

    class Example extends HookWidget {

    Widget build(BuildContext context) {
    return ElevatedButton(
    onPressed: () {
    // _Gerçekten_ "build" metodunun içinde değil, bunun yerine
    // bir kullanıcı etkileşimi yaşam döngüsünün içinde (burada "on pressed").
    final controller = useAnimationController();
    },
    child: Text('click me'),
    );
    }
    }
  • Koşullu olarak veya bir döngü içinde kullanılamazlar.

    Kötü:

    class Example extends HookWidget {
    const Example({required this.condition, super.key});
    final bool condition;

    Widget build(BuildContext context) {
    if (condition) {
    // Hook'lar "if"lerin/"for"ların içinde kullanılmamalıdır, ...
    final controller = useAnimationController();
    }
    ...
    }
    }

Hook'lar hakkında daha fazla bilgi için flutter_hooks'a bakın.

Hook'lar ve Riverpod

Kurulum

Hook'lar Riverpod'dan bağımsız olduğu için ayrıca kurulmaları gerekir. Onları kullanmak isterseniz, hooks_riverpod paketini kurmanız yeterli değildir. Ayrıca bağımlılıklarınıza flutter_hooks'u da eklemeniz gerekir. Daha fazla bilgi için Başlarken) sayfasına bakın.

Kullanım

Bazı durumlarda hem hook'ları hem de Riverpod'u kullanan bir Widget yazmak isteyebilirsiniz. Ancak muhtemelen fark ettiğiniz gibi, hem hook'lar hem de Riverpod kendi özel widget temel türlerini sunar: HookWidget ve ConsumerWidget.
Fakat sınıflar aynı anda yalnızca tek bir üst sınıftan türeyebilir.

Bu sorunu çözmek için hooks_riverpod paketini kullanabilirsiniz. Bu paket, hem HookWidget'ı hem de ConsumerWidget'ı tek bir türde birleştiren bir HookConsumerWidget sınıfı sunar.
Dolayısıyla HookWidget yerine HookConsumerWidget'tan türetebilirsiniz:

// We extend HookConsumerWidget instead of HookWidget
class Example extends HookConsumerWidget {

Widget build(BuildContext context, WidgetRef ref) {
// We can use both hooks and providers here
final counter = useState(0);
final value = ref.watch(myProvider);

return Text('Hello $counter $value');
}
}

Alternatif olarak, her iki paketin sunduğu "builder"ları kullanabilirsiniz.
Örneğin StatelessWidget kullanmayı sürdürüp hem HookBuilder hem de Consumer kullanabiliriz.

class Example extends StatelessWidget {

Widget build(BuildContext context) {
// We can use the builders provided by both packages
return Consumer(
builder: (context, ref, child) {
return HookBuilder(
builder: (context) {
final counter = useState(0);
final value = ref.watch(myProvider);

return Text('Hello $counter $value');
},
);
},
);
}
}
NOT

Bu yaklaşım hooks_riverpod kullanmadan da işe yarar. Yalnızca flutter_riverpod yeterlidir.

Bu yaklaşımı beğendiyseniz, hooks_riverpod her iki builder'ı tek bir yerde birleştiren HookConsumer'ı sunarak bunu kolaylaştırır:

class Example extends StatelessWidget {

Widget build(BuildContext context) {
// Equivalent to using both Consumer and HookBuilder.
return HookConsumer(
builder: (context, ref, child) {
final counter = useState(0);
final value = ref.watch(myProvider);

return Text('Hello $counter $value');
},
);
}
}