flutter_secure_storage icon indicating copy to clipboard operation
flutter_secure_storage copied to clipboard

Typed `flutter_secure_storage`

Open ViscousPot opened this issue 6 months ago • 0 comments

I've always found it a bit of a struggle that everything in flutter_secure_storage is stored and retrieved as Strings, so I spent a little while trying to make a type wrapper for flutter_secure_storage and have come up with the code attached below.

While this worked quite well for my own uses, I haven't been able to get around having to define the name of the enum (StorageKey in this case) inside the code for the wrapper. I imagine that, if it were possible to pass in the enum and retain the type derivations that StorageKey<N> is providing, it would be quite trivial to implement a more comprehensive typed implementation.

I am currently using this with success within my own app and haven't found any glaring issues thus far, so I hope that, at a minimum, this can be useful for those who want this.

Highlights

  • Returns the correct type (nullable and not, based on the definition in StorageKey)
  • Correctly types the inputs for the set function to prevent erroneous input

import 'package:flutter_secure_storage/flutter_secure_storage.dart';

enum StorageKey<T> {
  // Repo Manager
  repoIndex<int>(defaultValue: 0),
  tileSyncIndex<int>(defaultValue: 0),
  tileManualSyncIndex<int>(defaultValue: 0),
  onboardingStep<int>(defaultValue: 0),
  erroring<String?>(defaultValue: null),
  ghSponsorToken<String?>(defaultValue: null),
  repoNames<List<String>>(defaultValue: []);

  const StorageKey({required this.defaultValue});
  final T defaultValue;
}

Type getType<T>() => T;

class Storage<T extends StorageKey> {
  final FlutterSecureStorage storage;

  Storage(String? name)
    : storage = FlutterSecureStorage(aOptions: AndroidOptions(sharedPreferencesName: name), iOptions: IOSOptions(accountName: name));

  Future<N> get<N>(StorageKey<N> key) async {
    String? value = await storage.read(key: key.name.toString());

    if (N == getType<String?>() || N == getType<String>()) {
      if (null is N) {
        return value as N;
      }

      return (value ?? key.defaultValue) as N;
    }

    if (N == getType<int?>() || N == getType<int>()) {
      final finalValue = (value == null ? null : int.tryParse(value));

      if (null is N) {
        return finalValue as N;
      }

      return (finalValue ?? key.defaultValue) as N;
    }

    if (N == getType<List<String>?>() || N == getType<List<String>>()) {
      final finalValue = value?.split(",");

      if (null is N) {
        return finalValue as N;
      }

      return (finalValue ?? key.defaultValue) as N;
    }

    throw Exception("Key <${key.name.toString()}> datatype unsupported!");
  }

  Future<void> set<N>(StorageKey<N> key, N value) async {
    if (N == getType<int?>() || N == getType<int>()) {
      await storage.write(key: key.name.toString(), value: (value ?? "").toString());
      return;
    }

    if (N == getType<String?>() || N == getType<String>()) {
      await storage.write(key: key.name.toString(), value: value.toString());
      return;
    }

    if (N == getType<List<String>?>() || N == getType<List<String>>()) {
      await storage.write(key: key.name.toString(), value: value == null ? "" : (value as List<String>).join(","));
      return;
    }

    throw Exception("Key <${key.name.toString()}> datatype unsupported!");
  }
}

ViscousPot avatar May 09 '25 07:05 ViscousPot