get_secure_storage_plus 2.0.0
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.
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()),
),
);
},
),
)
],
),
),
);
}
}