get_secure_storage_plus 2.0.0 copy "get_secure_storage_plus: ^2.0.0" to clipboard
get_secure_storage_plus: ^2.0.0 copied to clipboard

A fast, extra-light, synchronous key-value storage with production-grade AES-GCM-256 encryption. A highly secure alternative to get_storage.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:get_secure_storage_plus/get_secure_storage_plus.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const App());
}

class App extends StatefulWidget {
  const App({Key? key}) : super(key: key);

  @override
  State<App> createState() => _AppState();
}

class _AppState extends State<App> {
  final TextEditingController _passwordController =
      TextEditingController(text: 'strongpassword');
  GetSecureStorage? box;

  bool _isInit = false;
  String _errorMessage = "";

  int _counter = 0;
  VoidCallback? _listenerRemover;

  @override
  void dispose() {
    _passwordController.dispose();
    _listenerRemover?.call();
    box?.dispose();
    super.dispose();
  }

  Future<void> _initStorage() async {
    try {
      setState(() {
        _errorMessage = "";
      });

      await GetSecureStorage.init(
        password: _passwordController.text,
        useBase64: false, // Configured for v2.0.0 optimizations
      );

      box = GetSecureStorage();

      // Setup listener test
      _listenerRemover = box!.listenKey('counter', (val) {
        if (mounted) {
          setState(() {
            _counter = val ?? 0;
          });
        }
      });

      setState(() {
        _isInit = true;
        _counter = box!.read('counter') ?? 0;
      });
    } catch (e) {
      setState(() {
        _errorMessage = "Init failed: \n$e";
      });
    }
  }

  String get lastupdated => box?.read('lastupdated') ?? 'never';
  bool get isDark => box?.read('darkmode') ?? false;

  void changeTheme(bool val) {
    if (box == null) return;
    box!.write('darkmode', val);
    box!.write('lastupdated', DateTime.now().toLocal().toString());
    setState(() {});
  }

  void incrementCounter() {
    if (box == null) return;
    box!.write('counter', _counter + 1);
  }

  void _disposeStorage() async {
    if (box != null) {
      await box!.dispose(); // Tests memory leak fix
      _listenerRemover = null;
      setState(() {
        _isInit = false;
        box = null;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: isDark ? ThemeData.dark() : ThemeData.light(),
      home: Builder(
        builder: (BuildContext innerContext) {
          return !_isInit
              ? _buildLoginScreen(innerContext)
              : _buildMainScreen(innerContext);
        },
      ),
    );
  }

  Widget _buildLoginScreen(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Login to Storage")),
      body: Padding(
        padding: const EdgeInsets.all(32.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(Icons.lock_outline, size: 80, color: Colors.blueAccent),
            const SizedBox(height: 20),
            TextField(
              controller: _passwordController,
              decoration: const InputDecoration(
                  labelText: "Password", border: OutlineInputBorder()),
              obscureText: true,
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: _initStorage,
              child: const Text("Initialize Main Storage"),
            ),
            const SizedBox(height: 20),
            if (_errorMessage.isNotEmpty) ...[
              Text(_errorMessage,
                  style: const TextStyle(color: Colors.red),
                  textAlign: TextAlign.center),
              const SizedBox(height: 20),
            ],
            const Divider(),
            const SizedBox(height: 10),
            OutlinedButton.icon(
              icon: const Icon(Icons.folder_shared),
              label: const Text("Open Data Migration Tester"),
              onPressed: () {
                // Navigate using a detached navigator since we build the MaterialApp locally
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (_) => const MigrationTesterScreen(),
                  ),
                );
              },
            ),
            const Padding(
              padding: EdgeInsets.symmetric(horizontal: 40.0, vertical: 10),
              child: Text(
                "Use the Migration Tester to manually inspect old or custom .gs files",
                textAlign: TextAlign.center,
                style: TextStyle(color: Colors.grey, fontSize: 12),
              ),
            )
          ],
        ),
      ),
    );
  }

  Widget _buildMainScreen(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("GetSecureStorage v2"),
        actions: [
          IconButton(
            icon: const Icon(Icons.logout),
            onPressed: _disposeStorage,
            tooltip: "Dispose Storage",
          ),
        ],
      ),
      body: Column(
        children: [
          Expanded(
            flex: 3,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                SwitchListTile(
                  value: isDark,
                  title: const Text("Touch to change ThemeMode"),
                  onChanged: changeTheme,
                ),
                const SizedBox(height: 10),
                Text('Last updated: $lastupdated'),
                const SizedBox(height: 40),
                const Divider(),
                const SizedBox(height: 30),
                Text('Reactive Counter (tests listenKey): $_counter',
                    style: const TextStyle(
                        fontSize: 18, fontWeight: FontWeight.bold)),
                const SizedBox(height: 20),
                ElevatedButton(
                  onPressed: incrementCounter,
                  child: const Text("Increment Counter"),
                ),
                const SizedBox(height: 20),
                const Padding(
                  padding: EdgeInsets.symmetric(horizontal: 40.0),
                  child: Text(
                    "To test the wrong password protection, press the logout button and try logging in with an invalid password. The data will not be wiped.",
                    textAlign: TextAlign.center,
                    style: TextStyle(color: Colors.grey),
                  ),
                )
              ],
            ),
          ),
        ],
      ),
    );
  }
}

// ─── MIGRATION TESTER SCREEN ────────────────────────────────────────────────

class MigrationTesterScreen extends StatefulWidget {
  const MigrationTesterScreen({Key? key}) : super(key: key);

  @override
  State<MigrationTesterScreen> createState() => _MigrationTesterScreenState();
}

class _MigrationTesterScreenState extends State<MigrationTesterScreen> {
  final TextEditingController _containerController =
      TextEditingController(text: 'GetSecureStorage');
  final TextEditingController _passwordController =
      TextEditingController(text: 'strongpassword');

  String _result = "";
  Map<String, dynamic> _data = {};

  @override
  void dispose() {
    _containerController.dispose();
    _passwordController.dispose();
    super.dispose();
  }

  void _testMigration() async {
    final container = _containerController.text.trim();
    final password = _passwordController.text.trim();

    if (container.isEmpty || password.isEmpty) return;

    setState(() {
      _result = "Decrypting & Reading...";
      _data = {};
    });

    try {
      // Init securely reads, detects any v1 data, and silently migrates it!
      await GetSecureStorage.init(
        container: container,
        password: password,
      );

      final box = GetSecureStorage(container: container, password: password);
      final keysIterable = box.getKeys();
      final keys = (keysIterable as Iterable).map((e) => e.toString()).toList();

      final Map<String, dynamic> loadedData = {};
      for (var k in keys) {
        loadedData[k] = box.read(k);
      }

      setState(() {
        _result = "Success! Found ${keys.length} keys inside database.";
        _data = loadedData;
      });

      // Cleanup memory footprint
      await box.dispose();
    } catch (e) {
      setState(() {
        _result = "Failed: $e";
        _data = {};
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Migration & File Tester')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: _containerController,
              decoration: const InputDecoration(
                labelText: 'Container Name (File Name)',
                helperText: 'e.g. GetSecureStorage',
              ),
            ),
            const SizedBox(height: 16),
            TextField(
              controller: _passwordController,
              decoration: const InputDecoration(
                labelText: 'AES Password',
                helperText: 'The password assigned to this file',
              ),
            ),
            const SizedBox(height: 24),
            ElevatedButton(
              onPressed: _testMigration,
              style: ElevatedButton.styleFrom(
                  minimumSize: const Size(double.infinity, 50)),
              child: const Text('Decrypt & Read File Data'),
            ),
            const SizedBox(height: 20),
            Text(
              _result,
              style: TextStyle(
                fontWeight: FontWeight.bold,
                fontSize: 16,
                color: _result.startsWith("Failed") ? Colors.red : Colors.green,
              ),
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 10),
            const Divider(),
            Expanded(
              child: ListView.builder(
                itemCount: _data.length,
                itemBuilder: (context, index) {
                  final key = _data.keys.elementAt(index);
                  final value = _data[key];
                  return Card(
                    margin: const EdgeInsets.symmetric(vertical: 4),
                    child: ListTile(
                      title: Text(key,
                          style: const TextStyle(fontWeight: FontWeight.bold)),
                      subtitle: Text(value.toString()),
                    ),
                  );
                },
              ),
            )
          ],
        ),
      ),
    );
  }
}
1
likes
160
points
88
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A fast, extra-light, synchronous key-value storage with production-grade AES-GCM-256 encryption. A highly secure alternative to get_storage.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

cryptography, flutter, get, path_provider, web

More

Packages that depend on get_secure_storage_plus