Created with <3 with dartpad.dev.
Last active
May 23, 2023 14:40
-
-
Save gnunicorn/83260f97980fbafd307d3321ac776832 to your computer and use it in GitHub Desktop.
decadent-dart-7321
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file | |
// for details. All rights reserved. Use of this source code is governed by a | |
// BSD-style license that can be found in the LICENSE file. | |
import 'dart:convert'; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter_riverpod/flutter_riverpod.dart'; | |
import 'package:shared_preferences/shared_preferences.dart'; | |
enum Feature { | |
tasks, | |
notes; | |
static get defaults => [Feature.notes]; | |
} | |
extension on Enum { | |
String keyName() => name; | |
} | |
Features<T> featuresFromJson<T extends Enum>( | |
List<dynamic> json, List<T> defaultOn, Function fromString) { | |
return Features<T>(flags: featureFlagsFromJson(json, fromString), defaultOn: defaultOn); | |
} | |
List<FeatureFlag<T>> featureFlagsFromJson<T extends Enum>( | |
List<dynamic> json, Function fromString) { | |
List<FeatureFlag<T>> flags = List.from(json.map((json) { | |
final key = json['key']!; | |
try { | |
final feature = fromString(key)!; | |
final active = json['active']; | |
return FeatureFlag<T>(feature: feature, active: active); | |
} catch (e) { | |
return null; | |
} | |
}).where((x) => x != null)); | |
return flags; | |
} | |
class FeatureFlag<T extends Enum> { | |
late T feature; | |
late bool active; | |
FeatureFlag({required this.feature, required this.active}); | |
Map toJson() { | |
Map fromObject = { | |
'key': feature.keyName(), | |
'active': active, | |
}; | |
return fromObject; | |
} | |
} | |
@immutable | |
class Features<T extends Enum> { | |
final List<FeatureFlag<T>> flags; | |
final List<T> defaultOn; | |
const Features({required this.flags, required this.defaultOn}); | |
String toJson() => json.encode(flags); | |
bool isActive(T feat) { | |
for (final flag in flags) { | |
if (flag.feature == feat) { | |
return flag.active; | |
} | |
} | |
return defaultOn.contains(feat); // default on check | |
} | |
Features<T> setActive(T feat, bool active) { | |
final List<FeatureFlag<T>> newFlags = List.from(flags); | |
newFlags.removeWhere((flag) => flag.feature == feat); | |
if (active || (defaultOn.contains(feat) && !active)) { | |
newFlags.add(FeatureFlag(feature: feat, active: active)); | |
} | |
return Features(flags: newFlags, defaultOn: defaultOn); | |
} | |
} | |
class FeaturesNotifier<T extends Enum> extends StateNotifier<Features<T>> { | |
FeaturesNotifier(initState): super(initState); | |
// Let's allow the UI to add todos. | |
void setActive(T f, bool active) { | |
state = state.setActive(f, active); | |
} | |
// Let's allow the UI to add todos. | |
void resetFeatures(List<FeatureFlag<T>> features) { | |
state = Features(flags: features, defaultOn: state.defaultOn); | |
} | |
} | |
class SharedPrefFeaturesNotifier extends FeaturesNotifier<Feature> { | |
late SharedPreferences prefInstance; | |
late String instanceKey; | |
SharedPrefFeaturesNotifier(this.instanceKey, initState): super(initState) { | |
_init(); | |
} | |
void _init() async { | |
debugPrint("start of init"); | |
prefInstance = await SharedPreferences.getInstance(); | |
debugPrint("got instance"); | |
final currentData = prefInstance.getString(instanceKey)?? '[]'; | |
final features = featureFlagsFromJson<Feature>( | |
json.decode(currentData), | |
(name) => Feature.values.byName(name)); | |
resetFeatures(features); | |
addListener((s) { | |
debugPrint("saving $s"); | |
prefInstance.setString(instanceKey, s.toJson()); | |
}); | |
debugPrint("end of init"); | |
} | |
} | |
final featuresProvider = | |
StateNotifierProvider<SharedPrefFeaturesNotifier, Features<Feature>>((ref){ | |
return SharedPrefFeaturesNotifier('a3.labs', Features<Feature>(flags: [], defaultOn: Feature.defaults)); | |
}); | |
// ---- Riverpod example app following. | |
// This is a reimplementation of the default Flutter application | |
// using riverpod. | |
void main() { | |
runApp( | |
// Adding ProviderScope enables Riverpod for the entire project | |
const ProviderScope(child: MyApp()), | |
); | |
} | |
/// Providers are declared globally and specify how to create a state | |
final counterProvider = StateProvider((ref) => 0); | |
class MyApp extends StatelessWidget { | |
const MyApp({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context) { | |
return const MaterialApp( | |
home: MyHomePage(), | |
); | |
} | |
} | |
class MyHomePage extends ConsumerWidget { | |
const MyHomePage({Key? key}) : super(key: key); | |
@override | |
Widget build(BuildContext context, WidgetRef ref) { | |
final features = ref.watch(featuresProvider); | |
final featureToggles = List.from(Feature.values.map((f) => SwitchListTile( | |
title: Text(f.name), | |
value: features.isActive(f), | |
onChanged: (bool? value) { | |
ref.read(featuresProvider.notifier).setActive(f, value!); | |
}, | |
))); | |
return Scaffold( | |
appBar: AppBar( | |
title: const Text('Riverpod example'), | |
), | |
body: Center( | |
child: Column( | |
mainAxisSize: MainAxisSize.min, | |
mainAxisAlignment: MainAxisAlignment.center, | |
children: <Widget>[ | |
const Text('You have pushed the button this many times:'), | |
Consumer( | |
builder: (context, ref, _) { | |
final count = ref.watch(counterProvider); | |
return Text( | |
'$count', | |
key: const Key('counterState'), | |
style: Theme.of(context).textTheme.headlineMedium, | |
); | |
}, | |
), | |
...featureToggles | |
], | |
), | |
), | |
floatingActionButton: FloatingActionButton( | |
key: const Key('increment_floatingActionButton'), | |
// The read method is a utility to read a provider without listening to it | |
onPressed: () => ref.read(counterProvider.notifier).state++, | |
tooltip: 'Increment', | |
child: const Icon(Icons.add), | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment