How to access Shared Preferences String in build method flutter

1,265

Solution 1

As @croxx5f and @AhmetKAYGISIZ suggested, I ended up using FutureBuilder to solve this problem. Thank you both so much for your help with this.

Here's the final code for anyone else who is stuck on this problem:

class MyWidget extends StatefulWidget {


  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  var prefs;

  @override
  void initState() {
    super.initState();
    getUserEmailFromSharedPrefs();
  }

Future<String> getUserEmailFromSharedPrefs() async {
    prefs = await SharedPreferences.getInstance();
    final userEmail = prefs.getString('userEmail') ?? '';
    return userEmail;
  }

  @override
  Widget build(BuildContext context) {

    return FutureBuilder(
                future: getUserEmailFromSharedPrefs(),
                builder: (context, AsyncSnapshot<String> snapshot) {
                  if(snapshot.hasData) {
                   return SingleChildScrollView(
                     scrollDirection: Axis.horizontal,
                     child: StreamBuilder<QuerySnapshot>(
                       stream: _firestore.collection(snapshot.data).orderBy('time', descending: false).snapshots(),
...

So in summary, I wrapped my streambuilder in a futurebuilder.

Solution 2

You can use WidgetsBinding.instance.addPostFrameCallback, this helps you to run a callback during a frame, just after the persistent frame callbacks (which is when the main rendering pipeline has been flushed). If a frame is in progress and post-frame callbacks haven't been executed yet, then the registered callback is still executed during the frame. Otherwise, the registered callback is executed during the next frame.

In code, you can use it something like this.

@override
  Widget build(BuildContext context) {
    WidgetsBinding.instance.addPostFrameCallback((_) async {
      final prefs = await SharedPreferences.getInstance();
      final userEmail = prefs.getString('userEmail') ?? '';
    });

Hope this answers your question.

Solution 3

Long Story short you should not perform any side effects inside your build method . See here


class MyWidget extends StatefulWidget {


  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  late final prefs ;

  @override
  void initState() async{
super.initState();
prefs  = await SharedPreferences.getInstance();
  }
  @override
  Widget build(BuildContext context) {

    return FutureBuilder(
      future: prefs,
      builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
        if(snapshot.hasData){}else if (snapshot.hasError)
          return Center(child: CircularProgressIndicator());
        
      },);
  }

}

As your build method can be called multiple times you should not perform network calls or call complex methods because as the docs say. This method can and will be called multiple times.

In your case I used a FutureBuilder to handle the future's state and awaited it in the initState insida a stateful widget.

Check this article for more info

Share:
1,265
Jason Lloyd
Author by

Jason Lloyd

Updated on December 29, 2022

Comments

  • Jason Lloyd
    Jason Lloyd over 1 year

    I'm trying to access userEmail in shared preferences, inside my build method. Here's some of the code for context:

      Widget build(BuildContext context) {
        final prefs = await SharedPreferences.getInstance();
        final userEmail = prefs.getString('userEmail') ?? '';
    ...
    Return Scaffold(
    body: SingleChildScrollView(
                    scrollDirection: Axis.horizontal,
                    child: StreamBuilder<QuerySnapshot>(
                      stream: _firestore.collection(userEmail).orderBy('time', descending: false).snapshots(),
    ...
    

    The issue I have is, an error comes up highlighting the await. When i hover over it with my cursor for more info, it say The await expression can only be used in an async function. Try marking the function body with either 'async' or 'async*'.

    There is then an option to add 'async' modifier. So i clicked that, which transformed code into this:

    Future<Widget> build(BuildContext context) async {
    ...
    
    

    This causes another error message: '_HomeScreenState.build' ('Future<Widget> Function(BuildContext)') isn't a valid override of 'State.build' ('Widget Function(BuildContext)').

    Any ideas how to solve this issue? I've tried saving the userEmail using the Provider package. This works perfectly when the user first signs in or registers, but if you hot reload, the stream doesn't work.

    • Ahmet KAYGISIZ
      Ahmet KAYGISIZ almost 3 years
      SharedPreferences is an async work. You cannot use it like this. Yo need to use Future.builder
    • Jason Lloyd
      Jason Lloyd almost 3 years
      Hi @AhmetKAYGISIZ thanks for the response. Are you saying I need to use FutureBuilder instead of StreamBuilder?
    • Ahmet KAYGISIZ
      Ahmet KAYGISIZ almost 3 years
      No brother, first, you should not write functions in build function. You can use initState for this. First you get your shared preferences, then you will build your StreamBuilder. link this issue is like yours. You can check it. If you have more questions, please ask.
  • Ahmet KAYGISIZ
    Ahmet KAYGISIZ almost 3 years
    Hi, is it a better solution or FutureBuilder?
  • Jason Lloyd
    Jason Lloyd almost 3 years
    Hi Darsh, thank you for your response to my question. I tried implementing what you suggested, but now my streambuilder is getting an eror: Undefined name 'userEmail'. Any ideas?
  • Jason Lloyd
    Jason Lloyd almost 3 years
    Hi @croxx5f thank you for your response to my question. So for a bit more context, the list of widgets that my streambuilder is returning are basically tasks that the user can press a text button to 'complete task', which then updates the document in firestore, and should then should immediately update the stream. If I use futurebuilder, will that happen, or will the user have to reload the page to see the latest, most up to date stream?
  • croxx5f
    croxx5f almost 3 years
    Hi @JasonLloyd, the user won't have to refresh the page, the futurebuilder is above the stream and its future it's stored in the prefs variable inside the state so is preserved between rebuilds(the email). The stream is below and will continue to rebuild as normally.
  • Darsh Shah
    Darsh Shah almost 3 years
    Yes, its a better solution than using FutureBuilder as a SharedPrefs are readily available.
  • croxx5f
    croxx5f almost 3 years
    Hi @JasonLloyd , please mark any of the answers as accepted so that future members can see it is solved before entering the post and having to read all of the answers.
  • Jason Lloyd
    Jason Lloyd almost 3 years
    Hi @croxx5f, just tried to but it says I need to wait 2 days to accept my own answer. But yes I will do as soon as I am allowed to. Thank you for all of your help, really grateful, you basically solved my problem straight away, I only had to adjust the code slightly