flutter:How to persist enum in shared preference?

Viewed 2825

I'm working with Enums through my application and I noticed that it resets every time I leave the page or of course when I close the app, I know about shared_preferences but I don't know if it's the right way or actually how to implement it with an enum. Any information on this would be much appreciated here is a sample from my application:

`enum Gender {
  male,
  female,
}

class InputPage extends StatefulWidget {
  @override
  _InputPageState createState() => _InputPageState();
}

class _InputPageState extends State<InputPage> {
  Gender selectedGender;
  int height = 180;
  int weight = 60;
  int age = 20;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('BMI CALCULATOR'),
      ),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          Expanded(
              child: Row(
            children: <Widget>[
              Expanded(
                child: ReusableCard(
                  onPress: () {
                    setState(() {
                      selectedGender = Gender.male;
                    });
                  },
                  colour: selectedGender == Gender.male
                      ? kActiveCardColour
                      : kInactiveCardColour,
                  cardChild: IconContent(
                    icon: FontAwesomeIcons.mars,
                    label: 'MALE',
                  ),
                ),
              ),
              Expanded(
                child: ReusableCard(
                  onPress: () {
                    setState(() {
                      selectedGender = Gender.female;
                    });
                  },
                  colour: selectedGender == Gender.female
                      ? kActiveCardColour
                      : kInactiveCardColour,
                  cardChild: IconContent(
                    icon: FontAwesomeIcons.venus,
                    label: 'FEMALE',
                  ),
                ),
              ),
            ],
          ))

,`

2 Answers

You can copy paste run full code below
You can use package https://pub.dev/packages/enum_to_string to save and get enum as String
Step 1: init selectedGender in initState() with EnumToString.fromString

@override
  void initState() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      initGender();
    });
    super.initState();
  }

  void initGender() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    selectedGender =
        EnumToString.fromString(Gender.values, prefs.getString("gender"));
    setState(() {});
  }

Step 2: Save selectedGender with prefs.setString("gender", enumToString.parse(Gender.male));

    onPress: () async {
            setState(() {
              selectedGender = Gender.male;
            });
            SharedPreferences prefs =
                await SharedPreferences.getInstance();
            prefs.setString(
                "gender", EnumToString.parse(Gender.male));
          }

working demo

enter image description here

full code

import 'package:flutter/material.dart';
import 'package:enum_to_string/enum_to_string.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';

const kBottomContainerHeight = 80.0;
const kActiveCardColour = Color(0xFF1D1E33);
const kInactiveCardColour = Color(0xFF111328);
const kBottomContainerColour = Color(0xFFEB1555);

const kLabelTextStyle = TextStyle(
  fontSize: 18.0,
  color: Color(0xFF8D8E98),
);

const kNumberTextStyle = TextStyle(
  fontSize: 50.0,
  fontWeight: FontWeight.w900,
);

const kLargeButtonTextStyle = TextStyle(
  fontSize: 25.0,
  fontWeight: FontWeight.bold,
);

const kTitleTextStyle = TextStyle(
  fontSize: 50.0,
  fontWeight: FontWeight.bold,
);

const kResultTextStyle = TextStyle(
  color: Color(0xFF24D876),
  fontSize: 22.0,
  fontWeight: FontWeight.bold,
);

const kBMITextStyle = TextStyle(
  fontSize: 100.0,
  fontWeight: FontWeight.bold,
);

const kBodyTextStyle = TextStyle(
  fontSize: 22.0,
);

class IconContent extends StatelessWidget {
  IconContent({this.icon, this.label});

  final IconData icon;
  final String label;

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Icon(
          icon,
          size: 80.0,
        ),
        SizedBox(
          height: 15.0,
        ),
        Text(
          label,
          style: kLabelTextStyle,
        )
      ],
    );
  }
}

enum Gender {
  male,
  female,
}

class ReusableCard extends StatelessWidget {
  ReusableCard({@required this.colour, this.cardChild, this.onPress});

  final Color colour;
  final Widget cardChild;
  final Function onPress;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onPress,
      child: Container(
        child: cardChild,
        margin: EdgeInsets.all(15.0),
        decoration: BoxDecoration(
          color: colour,
          borderRadius: BorderRadius.circular(10.0),
        ),
      ),
    );
  }
}

class InputPage extends StatefulWidget {
  @override
  _InputPageState createState() => _InputPageState();
}

class _InputPageState extends State<InputPage> {
  Gender selectedGender;
  int height = 180;
  int weight = 60;
  int age = 20;

  @override
  void initState() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      initGender();
    });
    super.initState();
  }

  void initGender() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    selectedGender =
        EnumToString.fromString(Gender.values, prefs.getString("gender"));
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('BMI CALCULATOR'),
        ),
        body: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: <Widget>[
              Expanded(
                  child: Row(
                children: <Widget>[
                  Expanded(
                    child: ReusableCard(
                      onPress: () async {
                        setState(() {
                          selectedGender = Gender.male;
                        });
                        SharedPreferences prefs =
                            await SharedPreferences.getInstance();
                        prefs.setString(
                            "gender", EnumToString.parse(Gender.male));
                      },
                      colour: selectedGender == Gender.male
                          ? kActiveCardColour
                          : kInactiveCardColour,
                      cardChild: IconContent(
                        icon: FontAwesomeIcons.mars,
                        label: 'MALE',
                      ),
                    ),
                  ),
                  Expanded(
                    child: ReusableCard(
                      onPress: () async {
                        setState(() {
                          selectedGender = Gender.female;
                        });
                        SharedPreferences prefs =
                            await SharedPreferences.getInstance();
                        prefs.setString(
                            "gender", EnumToString.parse(Gender.female));
                      },
                      colour: selectedGender == Gender.female
                          ? kActiveCardColour
                          : kInactiveCardColour,
                      cardChild: IconContent(
                        icon: FontAwesomeIcons.venus,
                        label: 'FEMALE',
                      ),
                    ),
                  ),
                ],
              ))
            ]));
  }
}

Future<void> main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: InputPage(),
    );
  }
}

I think shared prefs only stores simple types. You could copy what json_serializable does and just decode a string value with a simple function and a mapping. This is their implementation but I'll add comments.

// _$CategoryEnumMap is a Map<dynamic, String> mapping from enum entry to string
const _$CategoryEnumMap = {
  Category.action: 'action',
  Category.drama: 'drama',
};

T _$enumDecode<T>(
  // enumValues would be _$CategoryEnumMap in this case
  Map<T, dynamic> enumValues,
  // source is the string value you would get from shared prefs
  dynamic source, {
  T unknownValue,
}) {
  if (source == null) {
    throw ArgumentError('A value must be provided. Supported values: '
        '${enumValues.values.join(', ')}');
  }

  final value = enumValues.entries
      .singleWhere((e) => e.value == source, orElse: () => null)
      ?.key;

  if (value == null && unknownValue == null) {
    throw ArgumentError('`$source` is not one of the supported values: '
        '${enumValues.values.join(', ')}');
  }

  return value ?? unknownValue;
}

You could get rid of the _$ stuff to make the function publicly accessible and I'm sure you could automatically create the EnumMap using a function like this.

enum Category {
  action,
  drama,
}

final Map<dynamic, String> CategoryEnumMap = Map.fromIterable(
  Category.values.map((e) => MapEntry(e, describeEnum(e))),
);
Related