Pembangunan Aplikasi Mudah Alih Flutter · Pelajaran

Mengekalkan Keadaan dengan HydratedBloc

Siri dan pulihkan keadaan BLoC secara automatik merentas permulaan semula aplikasi dengan hydrated_bloc.

Pelajaran 3 daripada 413 langkah

Mengekalkan Keadaan dengan HydratedBloc ialah pelajaran Pembangunan Aplikasi Mudah Alih Flutter percuma di CoddyKit. Ini ialah pelajaran 3 daripada 4. Sebanyak 3 pelajaran dalam laluan pembelajaran ini boleh dibaca sepenuhnya secara percuma — selepas itu, CoddyKit PRO membuka akses kepada semua pelajaran, serta latihan praktikal dengan penyunting kod terbina dalam dan tutor kecerdasan buatan yang tersedia 24/7. Pelajaran ini merupakan sebahagian daripada laluan pembelajaran Pembangunan Aplikasi Mudah Alih Flutter, dan kemajuan anda disegerakkan merentas web serta aplikasi CoddyKit. Kursus Pembangunan Aplikasi Mudah Alih Flutter merangkumi sejumlah 4 pelajaran.

Mengapa Keadaan BLoC Perlu Disimpan?

Secara lalai, Bloc atau Cubit kehilangan segala-galanya apabila proses aplikasi dihentikan. Apabila aplikasi dibuka semula, keadaan awal dijalankan semula, jadi pilihan terakhir pengguna, kandungan bakul, atau tema akan hilang.

hydrated_bloc menyelesaikan masalah ini tanpa memerlukan anda menulis kod simpan/muat secara manual. Ia secara telus:

  • Menukar setiap keadaan baharu kepada bentuk bersiri dan menyimpannya dalam storan setempat apabila keadaan itu dikeluarkan
  • Memulihkan keadaan terakhir secara automatik apabila BLoC dicipta semula

Ini amat sesuai untuk keutamaan antara muka, penanda orientasi awal, dan data sesi kecil yang perlu kekal selepas aplikasi dimulakan semula.

Persediaan dan Pemulaan Storan

Tambahkan pakej pada pubspec.yaml:

  • hydrated_bloc menyediakan HydratedBloc dan HydratedCubit
  • path_provider menyediakan direktori yang boleh ditulis pada peranti

Sebelum aplikasi anda berjalan, anda mesti membina bahagian belakang storan dan menetapkannya kepada HydratedBloc.storage. Oleh sebab langkah ini melibatkan saluran platform, bungkusnya dengan WidgetsFlutterBinding.ensureInitialized().

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  HydratedBloc.storage = await HydratedStorage.build(
    storageDirectory: kIsWeb
        ? HydratedStorageDirectory.web
        : HydratedStorageDirectory(
            (await getApplicationDocumentsDirectory()).path,
          ),
  );

  runApp(const MyApp());
}

Daripada Cubit kepada HydratedCubit

Cara paling mudah untuk menyimpan keadaan ialah melanjutkan HydratedCubit dan bukannya Cubit. Anda mesti mengatasi dua kaedah:

  • toJson(state) menukar keadaan kepada Map yang boleh dikodkan sebagai JSON (atau mengembalikan null untuk melangkau penyimpanan)
  • fromJson(json) membina semula keadaan daripada peta tersebut (atau mengembalikan null untuk kembali kepada keadaan awal pembina)

Di sini, pembilang kekal selepas aplikasi dimulakan semula dengan hanya beberapa baris.

class CounterCubit extends HydratedCubit<int> {
  CounterCubit() : super(0);

  void increment() => emit(state + 1);
  void decrement() => emit(state - 1);

  @override
  int? fromJson(Map<String, dynamic> json) => json['value'] as int?;

  @override
  Map<String, dynamic>? toJson(int state) => {'value': state};
}

Cara fromJson dan toJson Mengawal Penyimpanan

Alirannya berlaku secara automatik sepenuhnya setelah kaedah ditakrifkan:

  • Pada setiap emit, hydrated_bloc memanggil toJson dan menulis hasilnya ke storan menggunakan storageToken BLoC sebagai kunci.
  • Apabila BLoC dibina, kelas asas membaca JSON yang disimpan dan memanggil fromJson; nilai yang dikembalikan menjadi keadaan permulaan, menggantikan nilai yang dihantar kepada super(...).

Jika fromJson mengembalikan null (belum ada data atau penyahkodan gagal), keadaan awal pembina digunakan. Kembali kepada nilai ini apabila null ialah jaring keselamatan anda.

Menyimpan Kelas Keadaan Tersuai

Aplikasi sebenar jarang menyimpan int biasa. Untuk keadaan tersuai, petakan setiap medan yang anda perlukan dalam toJson dan baca semula dalam fromJson. Di bawah, keadaan tetapan dengan penanda tema dan skala fon disiri sepenuhnya.

Kekalkan bentuk JSON yang stabil dan jelas supaya versi akan datang masih boleh membaca data lama.

class SettingsState {
  final bool darkMode;
  final double fontScale;
  const SettingsState({required this.darkMode, required this.fontScale});

  Map<String, dynamic> toMap() =>
      {'darkMode': darkMode, 'fontScale': fontScale};

  factory SettingsState.fromMap(Map<String, dynamic> m) => SettingsState(
        darkMode: m['darkMode'] as bool? ?? false,
        fontScale: (m['fontScale'] as num?)?.toDouble() ?? 1.0,
      );
}

class SettingsCubit extends HydratedCubit<SettingsState> {
  SettingsCubit()
      : super(const SettingsState(darkMode: false, fontScale: 1.0));

  void toggleDark() =>
      emit(SettingsState(darkMode: !state.darkMode, fontScale: state.fontScale));

  @override
  SettingsState? fromJson(Map<String, dynamic> json) =>
      SettingsState.fromMap(json);

  @override
  Map<String, dynamic>? toJson(SettingsState state) => state.toMap();
}

Menggunakan HydratedBloc dengan Peristiwa

Untuk logik dipacu peristiwa, lanjutkan HydratedBloc dan bukannya HydratedCubit. Kontrak toJson/fromJson adalah sama; hanya penghasilan keadaan yang berubah (anda mendaftarkan pengendali peristiwa dengan on<Event>).

Ini pilihan yang tepat apabila peralihan memerlukan semantik peristiwa, pengelogan, atau transformer.

sealed class CartEvent {}
class ItemAdded extends CartEvent {
  final String sku;
  ItemAdded(this.sku);
}

class CartBloc extends HydratedBloc<CartEvent, List<String>> {
  CartBloc() : super(const []) {
    on<ItemAdded>((event, emit) => emit([...state, event.sku]));
  }

  @override
  List<String>? fromJson(Map<String, dynamic> json) =>
      (json['items'] as List?)?.cast<String>();

  @override
  Map<String, dynamic>? toJson(List<String> state) => {'items': state};
}

Logik Penyirian Tulen (Boleh Diuji)

Teras penyimpanan ialah Dart biasa: menukar objek kepada Map dan menukarnya kembali. Anda boleh menguji kitaran pergi balik ini secara unit tanpa Flutter, storan, atau peranti. Di bawah ialah atur cara kendiri yang membuktikan pengekodan/penyahkodan tidak kehilangan data.

Anggap toJson/fromJson anda sebagai fungsi biasa dan ujinya secara berasingan sebelum menyambungkannya kepada BLoC.

import 'dart:convert';

Map<String, dynamic> toJson(int value) => {'value': value};
int? fromJson(Map<String, dynamic> json) => json['value'] as int?;

void main() {
  final state = 42;
  final encoded = jsonEncode(toJson(state));
  print('stored: $encoded');

  final decoded = fromJson(jsonDecode(encoded) as Map<String, dynamic>);
  print('restored: $decoded');
  print('lossless: ${decoded == state}');
}

Migrasi Skema dengan Medan Versi

Selepas aplikasi anda diterbitkan, data bersiri lama masih berada pada peranti pengguna. Jika anda menambah atau menamakan semula medan, fromJson yang naif boleh gagal atau membaca data yang rosak. Pertahanan standard ialah medan versi yang ditulis ke dalam JSON.

Semasa membaca, pilih cabang berdasarkan versi dan naik taraf bentuk lama. Demo ini memindahkan muatan v1 (satu name) kepada v2 (firstName/lastName).

import 'dart:convert';

Map<String, dynamic> migrate(Map<String, dynamic> json) {
  final version = json['v'] as int? ?? 1;
  if (version >= 2) return json;

  final parts = (json['name'] as String).split(' ');
  return {
    'v': 2,
    'firstName': parts.first,
    'lastName': parts.length > 1 ? parts.last : '',
  };
}

void main() {
  final oldData = jsonDecode('{"v":1,"name":"Ada Lovelace"}');
  final upgraded = migrate(oldData as Map<String, dynamic>);
  print(upgraded);
}

fromJson Berdaya Tahan: Mengendalikan Data Rosak

Jika fromJson menghasilkan ralat, hydrated_bloc menangkapnya dan kembali kepada keadaan awal, tetapi peta yang sah sebahagiannya masih boleh menghasilkan keadaan yang salah. Jadikan penyahkodan menyeluruh: sahkan jenis, sediakan nilai lalai, dan kembalikan null apabila data tidak boleh digunakan.

Mengembalikan null ialah tindakan yang disengajakan dan selamat — tindakan ini memberitahu hydrated_bloc supaya menggunakan keadaan awal pembina.

@override
SettingsState? fromJson(Map<String, dynamic> json) {
  try {
    final scale = (json['fontScale'] as num?)?.toDouble();
    if (scale == null || scale <= 0) return null; // reject bad data
    return SettingsState(
      darkMode: json['darkMode'] as bool? ?? false,
      fontScale: scale,
    );
  } catch (_) {
    return null; // fall back to initial state
  }
}

Penyimpanan Terpilih dan Pengosongan Keadaan

Anda tidak perlu menyimpan setiap keadaan. Mengembalikan null daripada toJson melangkau penulisan untuk keluaran keadaan itu — berguna untuk keadaan sementara memuatkan atau ralat yang tidak mahu dipulihkan.

Untuk memadam data yang disimpan, panggil clear() pada tika tersebut (hanya membuang entri BLoC ini) atau HydratedBloc.storage.clear() (memadamkan semuanya, contohnya semasa log keluar).

class AuthCubit extends HydratedCubit<AuthState> {
  AuthCubit() : super(const Unauthenticated());

  void logout() {
    clear();           // remove this cubit's persisted entry
    emit(const Unauthenticated());
  }

  @override
  Map<String, dynamic>? toJson(AuthState state) =>
      state is Authenticated ? {'token': state.token} : null; // skip others

  @override
  AuthState? fromJson(Map<String, dynamic> json) =>
      json['token'] is String ? Authenticated(json['token'] as String) : null;
}

Berbilang Tika dan storageToken

hydrated_bloc mengenal pasti data yang disimpan berdasarkan storageToken, yang secara lalai ialah runtimeType. Oleh itu, dua tika daripada kelas BLoC yang sama berkongsi satu ruang storan dan akan saling menimpa data.

Jika anda memerlukan penyimpanan mengikut entiti (contohnya, satu cubit bagi setiap bilik sembang), atasi id supaya setiap tika mendapat token unik seperti ChatCubit-room42.

class ChatCubit extends HydratedCubit<List<String>> {
  final String roomId;
  ChatCubit(this.roomId) : super(const []);

  @override
  String get id => roomId; // token becomes 'ChatCubit-$roomId'

  @override
  List<String>? fromJson(Map<String, dynamic> json) =>
      (json['messages'] as List?)?.cast<String>();

  @override
  Map<String, dynamic>? toJson(List<String> state) => {'messages': state};
}

Semakan Pantas

Seorang rakan sepasukan melaporkan bahawa HydratedCubit mengeluarkan keadaan sementara Loading, dan selepas aplikasi dimulakan semula, antara muka kadangkala tersekat memaparkan pemutar yang tidak pernah selesai. Apakah pembaikan yang paling bersih?

Rumusan

Kini anda boleh menyimpan keadaan BLoC merentas permulaan semula dengan hydrated_bloc:

  • Mulakan HydratedBloc.storage dalam main() selepas ensureInitialized().
  • Lanjutkan HydratedCubit atau HydratedBloc dan atasi toJson/fromJson.
  • Pemulihan berlaku secara automatik semasa pembinaan; null daripada fromJson kembali kepada keadaan awal dengan selamat.
  • Kembalikan null daripada toJson untuk melangkau penyimpanan keadaan sementara, dan gunakan clear() semasa log keluar.
  • Lindungi daripada data rosak dengan penyahkodan berdaya tahan dan migrasi berasaskan versi.
  • Atasi id apabila anda memerlukan token storan bagi setiap tika.

Pastikan muatan yang disimpan kecil serta penyirian anda tulen dan diuji — itulah yang menjadikan keadaan selamat selepas permulaan semula boleh dipercayai pada skala besar.

Percuma untuk bermula

Pelajari Dart dengan tutor kecerdasan buatan — percuma

Tulis dan jalankan kod sebenar dalam pelayar anda, dapatkan bantuan segera daripada tutor kecerdasan buatan yang tersedia 24/7, dan sambung semula dari tempat anda berhenti di web atau dalam aplikasi.

Kursus
22
Pelajaran
88

Soalan Lazim

Adakah pelajaran “Mengekalkan Keadaan dengan HydratedBloc” percuma?

Ya — sebanyak 3 pelajaran dalam laluan pembelajaran Pembangunan Aplikasi Mudah Alih Flutter, termasuk “Mengekalkan Keadaan dengan HydratedBloc”, boleh dibaca sepenuhnya secara percuma di web ini. Selepas itu, CoddyKit PRO membuka akses kepada semua pelajaran, serta latihan interaktif dengan penyunting kod terbina dalam dan tutor kecerdasan buatan yang tersedia 24/7. Kursus Pembangunan Aplikasi Mudah Alih Flutter merangkumi sejumlah 4 pelajaran.

Apakah yang akan saya pelajari dalam “Mengekalkan Keadaan dengan HydratedBloc”?

Siri dan pulihkan keadaan BLoC secara automatik merentas permulaan semula aplikasi dengan hydrated_bloc. Anda berlatih Pembangunan Aplikasi Mudah Alih Flutter menggunakan kod praktikal yang dijalankan terus dalam pelayar, manakala tutor kecerdasan buatan 24/7 menjawab soalan anda semasa anda mengikuti pelajaran.

Adakah saya memerlukan pengalaman untuk memulakan Pembangunan Aplikasi Mudah Alih Flutter?

Tiada pengalaman terdahulu diperlukan. Pembelajaran Pembangunan Aplikasi Mudah Alih Flutter di CoddyKit disusun untuk pelajar daripada peringkat pemula hingga lanjutan, jadi anda boleh bermula di sini atau dari awal dan belajar mengikut kadar anda sendiri. Ini ialah pelajaran 3 daripada 4.

Berapa lamakah pelajaran “Mengekalkan Keadaan dengan HydratedBloc” diambil?

Kebanyakan pelajaran CoddyKit mengambil masa kira-kira 5–10 minit. Setiap pelajaran ringkas dan interaktif, jadi anda boleh membuat kemajuan secara berterusan dan menyambung tepat dari tempat anda berhenti di web atau aplikasi.

Bolehkah saya menulis dan menjalankan kod dalam pelajaran Pembangunan Aplikasi Mudah Alih Flutter ini?

Ya. Setiap pelajaran Pembangunan Aplikasi Mudah Alih Flutter menyertakan penyunting kod terbina dalam, jadi anda boleh menulis dan menjalankan kod sebenar terus dalam pelayar serta menerima maklum balas kecerdasan buatan serta-merta — tanpa memerlukan persediaan setempat.

Semua pelajaran dalam kursus ini

  1. Peristiwa, Keadaan dan Keputusan Cubit-berbanding-Bloc
  2. Pengubah Stream dan Penyahlantunan Peristiwa dalam BLoC
  3. Mengekalkan Keadaan dengan HydratedBloc
  4. Menguji BLoC dengan bloc_test dan Mocktail
← Kembali ke Pembangunan Aplikasi Mudah Alih Flutter