Mobilutvikling med Flutter · leksjon

Kodegenerering med riverpod_generator og @riverpod

Bruk riverpod_generator-annoteringene til å produsere typesikre providere uten boilerplate.

Leksjon 2 av 413 trinn

Kodegenerering med riverpod_generator og @riverpod er en gratis leksjon i Mobilutvikling med Flutter på CoddyKit. Dette er leksjon 2 av 4. Du kan lese valgfritt 3 leksjoner fra denne læringsstien gratis i sin helhet – deretter låser CoddyKit PRO opp alle leksjoner, samt praktisk øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Mobilutvikling med Flutter, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Mobilutvikling med Flutter inneholder totalt 4 leksjoner.

Hvorfor kodegenerering?

Før Riverpod 2.0 måtte De selv velge riktig providertype: Provider, StateProvider, FutureProvider, StreamProvider, NotifierProvider og så videre. Hvis De valgte feil, måtte koden skrives om.

Pakken riverpod_generator snur dette på hodet. De skriver en vanlig funksjon eller klasse og legger til annotasjonen @riverpod. Generatoren undersøker returtypen og oppretter den riktige, fullstendig typesikre provideren for Dem.

  • Mindre boilerplate — ingen manuelle provider-deklarasjoner.
  • Typesikre parametere — send argumenter uten .family-krumspring.
  • Auto-dispose som standard — genererte providers oppfører seg som autoDispose.

Legge til avhengighetene

Kodegenerering trenger både pakker for kjøring og for utvikling. riverpod_annotation inneholder annotasjonen @riverpod som De bruker i kildekoden. riverpod_generator og build_runner kjører byggesteget som genererer .g.dart-filene.

En typisk pubspec.yaml for en Flutter-app ser slik ut.

dependencies:
  flutter:
    sdk: flutter
  flutter_riverpod: ^2.5.1
  riverpod_annotation: ^2.3.5

dev_dependencies:
  build_runner: ^2.4.11
  riverpod_generator: ^2.4.0
  custom_lint: ^0.6.4
  riverpod_lint: ^2.3.10

Deres første genererte provider

Den minste genererte provideren er en funksjon på toppnivå som er annotert med @riverpod. Den første parameteren er alltid et Ref-objekt; returtypen avgjør alt.

Ettersom denne funksjonen returnerer en vanlig String synkront, genererer verktøyet en skrivebeskyttet provider som eksponerer denne verdien. De bruker den via ref.watch(helloWorldProvider), på nøyaktig samme måte som en manuelt skrevet Provider<String>.

Legg merke til de to nødvendige delene: part-direktivet og kommentaren // ignore_for_file er valgfri — men part 'file.g.dart'; er obligatorisk.

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'hello.g.dart';

@riverpod
String helloWorld(Ref ref) {
  return 'Hello, Riverpod 2.0';
}

Kjøre generatoren

Annotasjonen alene gjør ingenting før build_runner genererer den tilhørende .g.dart-filen. Kjør den fra prosjektroten.

  • Engangsbygging: genererer én gang og avslutter. --delete-conflicting-outputs fjerner foreldede genererte filer.
  • Overvåkingsmodus: genererer automatisk på nytt hver gang De lagrer en kildefil — ideelt under aktiv utvikling.

Når kjøringen er ferdig, blir symbolet helloWorldProvider tilgjengelig for import.

# Generate once
dart run build_runner build --delete-conflicting-outputs

# Or watch and rebuild on save
dart run build_runner watch --delete-conflicting-outputs

Returtypen styrer provideren

Generatoren leser returtypen og velger automatisk den tilsvarende providertypen. Dette er den viktigste fordelen med kodegenerering: De trenger aldri å angi en providertype igjen.

  • Returner T → synkron provider (som Provider<T>).
  • Returner Future<T> → asynkron provider som eksponerer AsyncValue<T> (som FutureProvider).
  • Returner Stream<T> → stream-provider som eksponerer AsyncValue<T> (som StreamProvider).

Nedenfor er det nok å endre signaturen til Future for å gjøre den om til en asynkron provider — ingen andre endringer er nødvendige.

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'user.g.dart';

@riverpod
Future<String> userName(Ref ref) async {
  await Future<void>.delayed(const Duration(seconds: 1));
  return 'Ada Lovelace';
}

Sende parametere (slutt på .family)

Med manuelt skrevne providers betydde parameterisering at De måtte bruke .family og ett enkelt tuppel-lignende argument. Generatoren lar Dem legge til vanlige funksjonsparametere etter ref, og de blir sterkt typede provider-argumenter.

Her tar messageProvider imot en int id. De kaller den som ref.watch(messageProvider(42)). Flere parametere samt navngitte og valgfrie parametere fungerer også.

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'message.g.dart';

@riverpod
Future<String> message(Ref ref, int id) async {
  final repo = ref.watch(messageRepositoryProvider);
  return repo.fetchById(id);
}

// Usage in a widget:
// final msg = ref.watch(messageProvider(42));

Tilstandsstyrt logikk: Notifier-klassen

For muterbar tilstand med metoder annoterer De en klasse som utvider den genererte basisklassen _$ClassName. De overstyrer build() for å returnere den opprinnelige tilstanden; generatoren kobler automatisk opp en NotifierProvider for Dem.

Inne i metodene endrer De state, og lyttere bygger automatisk grensesnittet på nytt. Dette erstatter den gamle kombinasjonen av Notifier og en manuell NotifierProvider-deklarasjon med én enkelt annotert klasse.

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'counter.g.dart';

@riverpod
class Counter extends _$Counter {
  @override
  int build() => 0;

  void increment() => state++;
  void reset() => state = 0;
}

Asynkrone Notifiers

Hvis build() returnerer en Future, genererer verktøyet en AsyncNotifier. Den eksponerte tilstanden er en AsyncValue<T> som automatisk følger med på laste-, data- og feiltilstander.

Hvis De vil oppdatere tilstanden etter en asynkron handling, tilordner De AsyncValue.guard(...) til state — den kjører den asynkrone koden og fanger opp suksess eller feil uten manuell try/catch.

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'todos.g.dart';

@riverpod
class Todos extends _$Todos {
  @override
  Future<List<String>> build() async {
    return ref.watch(todoRepositoryProvider).fetchAll();
  }

  Future<void> add(String title) async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(() async {
      await ref.read(todoRepositoryProvider).create(title);
      return ref.read(todoRepositoryProvider).fetchAll();
    });
  }
}

Bruke genererte providers

Genererte providers brukes på nøyaktig samme måte som manuelle providers — det genererte symbolet er <name>Provider for funksjoner eller <ClassName>Provider for Notifier-klasser.

  • ref.watch(counterProvider) → den gjeldende tilstandsverdien.
  • ref.read(counterProvider.notifier) → Notifier-instansen, slik at De kan kalle metoder som increment().
  • For asynkrone providers returnerer watch en AsyncValue som De håndterer med .when(...).
class CounterView extends ConsumerWidget {
  const CounterView({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(counterProvider);
    return Column(
      children: [
        Text('Count: $count'),
        ElevatedButton(
          onPressed: () => ref.read(counterProvider.notifier).increment(),
          child: const Text('Add'),
        ),
      ],
    );
  }
}

Keep-alive og avhengigheter

Genererte providers bruker auto-dispose som standard — de forkaster tilstanden når ingen lenger følger med på dem. To annotasjonsalternativer gir Dem kontroll:

  • @Riverpod(keepAlive: true) — holder provideren i live selv uten lyttere (brukes for appomfattende singletoner, som en Dio-klient).
  • @Riverpod(dependencies: [...]) — deklarerer lokale overstyringer for provider-scoping. De fleste apper trenger ikke dette.

Den store @Riverpod(...)-formen er bare den konfigurerbare versjonen av den korte formen @riverpod.

import 'package:dio/dio.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'http.g.dart';

@Riverpod(keepAlive: true)
Dio dio(Ref ref) {
  return Dio(BaseOptions(baseUrl: 'https://api.example.com'));
}

Ren Dart: Hvorfor logikken kan testes

En viktig gevinst ved kodegenerering er at provider-kroppene består av vanlige Dart-funksjoner og -klasser — de er enkle å forstå og enhetsteste. Nedenfor ser De en frittstående illustrasjon av den samme mutasjonslogikken med state++ som en generert Notifier ville kjørt, uten behov for Flutter- eller Riverpod-importer.

Denne typen rene logikk er nettopp det De bør holde inne i en generert @riverpod-klasse, slik at den forblir svært enkel å teste.

class Counter {
  int state = 0;
  void increment() => state++;
  void reset() => state = 0;
}

void main() {
  final counter = Counter();
  counter.increment();
  counter.increment();
  counter.increment();
  print('After 3 increments: ${counter.state}');
  counter.reset();
  print('After reset: ${counter.state}');
}

Kort kontroll

De annoterer en funksjon som returnerer Future<List<Product>> med @riverpod. Hva slags provider genererer riverpod_generator, og hvordan bruker De den i en widget?

Oppsummering

De har lært hvordan riverpod_generator fjerner boilerplate for providers:

  • Legg til riverpod_annotation (kjøring) samt riverpod_generator og build_runner (utvikling), og et part '<file>.g.dart';-direktiv.
  • Annoter en funksjon for skrivebeskyttede eller avledede verdier, eller en klasse som utvider _$Name for tilstandsbaserte Notifiers.
  • Returtypen velger provideren: T → synkron, Future<T> → asynkron (AsyncValue), Stream<T> → stream.
  • Legg til vanlige parametere etter ref i stedet for .family.
  • Kjør dart run build_runner watch for å generere på nytt når De lagrer.
  • Providers bruker auto-dispose som standard; bruk @Riverpod(keepAlive: true) for appomfattende singletoner.
Gratis å komme i gang

Lær deg Dart med en AI-veileder – gratis

Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.

Kurs
22
Leksjoner
88

Ofte stilte spørsmål

Er leksjonen «Kodegenerering med riverpod_generator og @riverpod» gratis?

Ja – du kan lese valgfritt 3 av leksjonene i læringsstien Mobilutvikling med Flutter, inkludert «Kodegenerering med riverpod_generator og @riverpod», gratis i sin helhet her på nettet. Deretter låser CoddyKit PRO opp alle leksjoner, samt interaktiv øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Kurset i Mobilutvikling med Flutter inneholder totalt 4 leksjoner.

Hva lærer jeg i «Kodegenerering med riverpod_generator og @riverpod»?

Bruk riverpod_generator-annoteringene til å produsere typesikre providere uten boilerplate. Du øver på Mobilutvikling med Flutter med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.

Trenger jeg erfaring for å begynne med Mobilutvikling med Flutter?

Ingen tidligere erfaring er nødvendig. Mobilutvikling med Flutter på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 2 av 4.

Hvor lang tid tar leksjonen «Kodegenerering med riverpod_generator og @riverpod»?

De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.

Kan jeg skrive og kjøre kode i denne Mobilutvikling med Flutter-leksjonen?

Ja. Alle Mobilutvikling med Flutter-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.

Alle leksjonene i dette kurset

  1. Fra Provider til Riverpod: Migrering av eldre tilstand
  2. Kodegenerering med riverpod_generator og @riverpod
  3. Datapipelines med AsyncNotifier og FutureProvider
  4. Provider-scope, overrides og ProviderObserver
← Tilbake til Mobilutvikling med Flutter