Basics

Penyimpanan

Pengantar

Nylo Website v7 menyediakan sistem penyimpanan yang powerful melalui class NyStorage. Secara internal menggunakan flutter_secure_storage untuk menyimpan data secara aman di perangkat pengguna.

import 'package:nylo_framework/nylo_framework.dart';

// Save a value
await NyStorage.save("coins", 100);

// Read a value
int? coins = await NyStorage.read<int>('coins'); // 100

// Delete a value
await NyStorage.delete('coins');

Menyimpan Nilai

Simpan nilai menggunakan NyStorage.save() atau helper storageSave():

// Using NyStorage class
await NyStorage.save("username", "Anthony");
await NyStorage.save("score", 1500);
await NyStorage.save("isPremium", true);

// Using helper function
await storageSave("username", "Anthony");

Simpan ke Backpack Secara Bersamaan

Simpan di penyimpanan persisten dan Backpack dalam memori sekaligus:

await NyStorage.save('auth_token', 'abc123', inBackpack: true);

// Now accessible synchronously via Backpack
String? token = Backpack.instance.read('auth_token');

Membaca Nilai

Baca nilai dengan type casting otomatis:

// Read as String (default)
String? username = await NyStorage.read('username');

// Read with type casting
int? score = await NyStorage.read<int>('score');
double? rating = await NyStorage.read<double>('rating');
bool? isPremium = await NyStorage.read<bool>('isPremium');

// With default value
String name = await NyStorage.read('name', defaultValue: 'Guest') ?? 'Guest';

// Using helper function
String? username = await storageRead('username');
int? score = await storageRead<int>('score');

Menghapus Nilai

// Delete a single key
await NyStorage.delete('username');

// Delete and remove from Backpack too
await NyStorage.delete('auth_token', andFromBackpack: true);

// Delete multiple keys
await NyStorage.deleteMultiple(['key1', 'key2', 'key3']);

// Delete all (with optional exclusions)
await NyStorage.deleteAll();
await NyStorage.deleteAll(excludeKeys: ['auth_token']);

Kunci Penyimpanan

Atur kunci penyimpanan Anda di lib/config/storage_keys.dart:

final class StorageKeysConfig {
  // Auth key for user authentication
  static StorageKey auth = 'SK_AUTH';

  // Keys synced on app boot
  static syncedOnBoot() => () async {
    return [
      coins.defaultValue(0),
      themePreference.defaultValue('light'),
    ];
  };

  static StorageKey coins = 'SK_COINS';
  static StorageKey themePreference = 'SK_THEME_PREFERENCE';
  static StorageKey onboardingComplete = 'SK_ONBOARDING_COMPLETE';
}

Menggunakan Ekstensi StorageKey

StorageKey adalah typedef untuk String, dan dilengkapi dengan sekumpulan metode ekstensi yang powerful:

// Save
await StorageKeysConfig.coins.save(100);

// Save with Backpack
await StorageKeysConfig.coins.save(100, inBackpack: true);

// Read
int? coins = await StorageKeysConfig.coins.read<int>();

// Read with default value
int? coins = await StorageKeysConfig.coins.fromStorage<int>(defaultValue: 0);

// Save/Read JSON
await StorageKeysConfig.coins.saveJson({"gold": 50, "silver": 200});
Map? data = await StorageKeysConfig.coins.readJson<Map>();

// Delete
await StorageKeysConfig.coins.deleteFromStorage();

// Delete (alias)
await StorageKeysConfig.coins.flush();

// Read from Backpack (synchronous)
int? coins = StorageKeysConfig.coins.fromBackpack<int>();

// Collection operations
await StorageKeysConfig.coins.addToCollection<int>(100);
List<int> allCoins = await StorageKeysConfig.coins.readCollection<int>();

Simpan/Baca JSON

Simpan dan ambil data JSON:

// Save JSON
Map<String, dynamic> user = {
  "name": "Anthony",
  "email": "anthony@example.com",
  "preferences": {"theme": "dark"}
};
await NyStorage.saveJson("user_data", user);

// Read JSON
Map<String, dynamic>? userData = await NyStorage.readJson("user_data");
print(userData?['name']); // "Anthony"

TTL (Time-to-Live)

Nylo Website v7 mendukung penyimpanan nilai dengan kedaluwarsa otomatis:

// Save with 1 hour expiration
await NyStorage.saveWithExpiry(
  'session_token',
  'abc123',
  ttl: Duration(hours: 1),
);

// Read (returns null if expired)
String? token = await NyStorage.readWithExpiry<String>('session_token');

// Check remaining time
Duration? remaining = await NyStorage.getTimeToLive('session_token');
if (remaining != null) {
  print('Expires in ${remaining.inMinutes} minutes');
}

// Clean up all expired keys
int removed = await NyStorage.removeExpired();
print('Removed $removed expired keys');

Operasi Batch

Tangani beberapa operasi penyimpanan secara efisien:

// Save multiple values
await NyStorage.saveAll({
  'username': 'Anthony',
  'score': 1500,
  'level': 10,
});

// Read multiple values
Map<String, dynamic?> values = await NyStorage.readMultiple<dynamic>([
  'username',
  'score',
  'level',
]);

// Delete multiple keys
await NyStorage.deleteMultiple(['temp_1', 'temp_2', 'temp_3']);

Pengantar Koleksi

Koleksi memungkinkan Anda menyimpan daftar item di bawah satu kunci:

// Add items to a collection
await NyStorage.addToCollection("favorites", item: "Product A");
await NyStorage.addToCollection("favorites", item: "Product B");

// Read the collection
List<String> favorites = await NyStorage.readCollection<String>("favorites");
// ["Product A", "Product B"]

Menambah ke Koleksi

// Add item (allows duplicates by default)
await NyStorage.addToCollection("cart_ids", item: 123);

// Prevent duplicates
await NyStorage.addToCollection(
  "cart_ids",
  item: 123,
  allowDuplicates: false,
);

// Save entire collection at once
await NyStorage.saveCollection<int>("cart_ids", [1, 2, 3, 4, 5]);

Membaca Koleksi

// Read collection with type
List<int> cartIds = await NyStorage.readCollection<int>("cart_ids");

// Check if collection is empty
bool isEmpty = await NyStorage.isCollectionEmpty("cart_ids");

Memperbarui Koleksi

// Update item by index
await NyStorage.updateCollectionByIndex<int>(
  0, // index
  (item) => item + 10, // transform function
  key: "scores",
);

// Update items matching a condition
await NyStorage.updateCollectionWhere<Map<String, dynamic>>(
  (item) => item['id'] == 5, // where condition
  key: "products",
  update: (item) {
    item['quantity'] = item['quantity'] + 1;
    return item;
  },
);

Menghapus dari Koleksi

// Delete by index
await NyStorage.deleteFromCollection<String>(0, key: "favorites");

// Delete by value
await NyStorage.deleteValueFromCollection<int>(
  "cart_ids",
  value: 123,
);

// Delete items matching a condition
await NyStorage.deleteFromCollectionWhere<Map<String, dynamic>>(
  (item) => item['expired'] == true,
  key: "coupons",
);

// Delete entire collection
await NyStorage.delete("favorites");

Penyimpanan Backpack

Backpack adalah class penyimpanan dalam memori yang ringan untuk akses sinkron cepat selama sesi pengguna. Data tidak disimpan ketika aplikasi ditutup.

Simpan ke Backpack

// Using helper
backpackSave('user_token', 'abc123');
backpackSave('user', userObject);

// Using Backpack directly
Backpack.instance.save('settings', {'darkMode': true});

Baca dari Backpack

// Using helper
String? token = backpackRead('user_token');
User? user = backpackRead<User>('user');

// Using Backpack directly
var settings = Backpack.instance.read('settings');

Hapus dari Backpack

backpackDelete('user_token');

// Delete all
backpackDeleteAll();

Contoh Praktis

// After login, store token in both persistent and session storage
Future<void> handleLogin(String token) async {
  // Persist for app restarts
  await NyStorage.save('auth_token', token);

  // Also store in Backpack for quick access
  backpackSave('auth_token', token);
}

// In API service, access synchronously
class ApiService extends NyApiService {
  Future<User?> getProfile() async {
    return await network<User>(
      request: (request) => request.get("/profile"),
      bearerToken: backpackRead('auth_token'), // No await needed
    );
  }
}

Persist dengan Backpack

Simpan di penyimpanan persisten dan Backpack dalam satu panggilan:

// Save to both
await NyStorage.save('user_token', 'abc123', inBackpack: true);

// Now accessible via Backpack (sync) and NyStorage (async)
String? tokenSync = Backpack.instance.read('user_token');
String? tokenAsync = await NyStorage.read('user_token');

Sinkronisasi Storage ke Backpack

Muat semua penyimpanan persisten ke Backpack saat boot aplikasi:

// In your app provider
await NyStorage.syncToBackpack();

// With overwrite option
await NyStorage.syncToBackpack(overwrite: true);

Sesi

Sesi menyediakan penyimpanan dalam memori dengan nama untuk mengelompokkan data terkait (tidak disimpan):

// Create/access a session and add data
session('checkout')
    .add('items', ['Product A', 'Product B'])
    .add('total', 99.99)
    .add('coupon', 'SAVE10');

// Or initialize with data
session('checkout', {
  'items': ['Product A', 'Product B'],
  'total': 99.99,
});

// Read session data
List<String>? items = session('checkout').get<List<String>>('items');
double? total = session('checkout').get<double>('total');

// Get all session data
Map<String, dynamic>? checkoutData = session('checkout').data();

// Delete a single value
session('checkout').delete('coupon');

// Clear entire session
session('checkout').clear();
// or
session('checkout').flush();

Persist Sesi

Sesi dapat disinkronkan ke penyimpanan persisten:

// Save session to storage
await session('checkout').syncToStorage();

// Restore session from storage
await session('checkout').syncFromStorage();

Kasus Penggunaan Sesi

Sesi ideal untuk:

  • Form multi-langkah (onboarding, checkout)
  • Preferensi pengguna sementara
  • Alur wizard/journey
  • Data keranjang belanja

Simpan Model

Class dasar Model menyediakan metode penyimpanan bawaan. Ketika Anda mendefinisikan key di constructor, model dapat menyimpan dirinya sendiri:

class User extends Model {
  String? name;
  String? email;

  // Define a storage key
  static StorageKey key = 'user';
  User() : super(key: key);

  User.fromJson(dynamic data) : super(key: key) {
    name = data['name'];
    email = data['email'];
  }

  @override
  Map<String, dynamic> toJson() => {
    "name": name,
    "email": email,
  };
}

Menyimpan Model

User user = User();
user.name = "Anthony";
user.email = "anthony@example.com";

// Save to persistent storage
await user.save();

// Save to both storage and Backpack
await user.save(inBackpack: true);

Membaca Kembali Model

User? user = await NyStorage.read<User>(User.key);

Sinkronisasi ke Backpack

Muat model dari penyimpanan ke Backpack untuk akses sinkron:

bool found = await User().syncToBackpack();
if (found) {
  User? user = Backpack.instance.read<User>(User.key);
}

Koleksi Model

Simpan model ke koleksi:

User userAnthony = User();
userAnthony.name = 'Anthony';
await userAnthony.saveToCollection();

User userKyle = User();
userKyle.name = 'Kyle';
await userKyle.saveToCollection();

// Read back
List<User> users = await NyStorage.readCollection<User>(User.key);

Referensi Ekstensi StorageKey

StorageKey adalah typedef untuk String. Ekstensi NyStorageKeyExt menyediakan metode-metode ini:

Metode Return Deskripsi
save(value, {inBackpack}) Future Simpan nilai ke penyimpanan
saveJson(value, {inBackpack}) Future Simpan nilai JSON ke penyimpanan
read<T>({defaultValue}) Future<T?> Baca nilai dari penyimpanan
readJson<T>({defaultValue}) Future<T?> Baca nilai JSON dari penyimpanan
fromStorage<T>({defaultValue}) Future<T?> Alias untuk read
fromBackpack<T>({defaultValue}) T? Baca dari Backpack (sinkron)
toModel<T>() T Konversi string JSON ke model
addToCollection<T>(value, {allowDuplicates}) Future Tambah item ke koleksi
readCollection<T>() Future<List<T>> Baca koleksi
deleteFromStorage({andFromBackpack}) Future Hapus dari penyimpanan
flush({andFromBackpack}) Future Alias untuk deleteFromStorage
defaultValue<T>(value) Future Function(bool)? Atur default jika kunci kosong (digunakan di syncedOnBoot)

Exception Penyimpanan

Nylo Website v7 menyediakan exception penyimpanan yang bertipe:

Exception Deskripsi
StorageException Exception dasar dengan pesan dan kunci opsional
StorageSerializationException Gagal melakukan serialisasi objek untuk penyimpanan
StorageDeserializationException Gagal melakukan deserialisasi data tersimpan
StorageKeyNotFoundException Kunci penyimpanan tidak ditemukan
StorageTimeoutException Operasi penyimpanan timeout
try {
  await NyStorage.read<User>('user');
} on StorageDeserializationException catch (e) {
  print('Failed to load user: ${e.message}');
  print('Expected type: ${e.expectedType}');
}

Migrasi Legacy

Nylo Website v7 menggunakan format penyimpanan envelope baru. Jika Anda mengupgrade dari v6, Anda dapat memigrasikan data lama:

// Call during app initialization
int migrated = await NyStorage.migrateToEnvelopeFormat();
print('Migrated $migrated keys to new format');

Ini mengkonversi format legacy (kunci _runtime_type terpisah) ke format envelope baru. Migrasi aman untuk dijalankan berkali-kali - kunci yang sudah dimigrasi akan dilewati.