Flutter: Access SharedPreferences Provider / ChangeNotifier in a Stream Class

Issue

I’ve looked around in StackoverFlow and was not able to find myself a solution to this.
Scenario:
I have a Flutter SharedPreferences Provider with ChangeNotifier Class, that will get updated with the current Logged In User info.

Simplified content:

class SharedPreferences {
  final String userId;
  final String userName;

  SharedPreferences({
    @required this.userId,
    @required this.userName,
  });
}
class SharedPreferencesData with ChangeNotifier {
  var _sharedPreferencesData = SharedPreferences(
    userId: 'testUserId',
    userName: 'testUserName',
  );}

And a database.dart file with Class containing DataBaseServices to get FireStore Streams from Snapshots:

class DatabaseService {
  final CollectionReference companiesProfilesCollection =
      Firestore.instance.collection('companiesProfiles');

  List<CompanyProfile> _companiesProfilesFromSnapshot(QuerySnapshot snapshot) {
    return snapshot.documents.map((doc) {
      return CompanyProfile(
        docId: doc.documentID,
        companyName: doc.data['companyName'] ?? '',
        maxLocationsNumber: doc.data['maxLocationsNumber'] ?? 0,
        maxUsersNumber: doc.data['maxUsersNumber'] ?? 0,
      );
    }).toList();
  }

  Stream<List<CompanyProfile>> get getCompaniesProfiles {
    return companiesProfilesCollection
        .where('userId', isEqualTo: _userIdFromProvider) 
        // My problem is above -----
        .snapshots()
        .map(_companiesProfilesFromSnapshot);
  }
}

I Don’t want to fetch the entire Stream data as it could be massive for other Streams, I just want to pass the userID under .where(‘userId’, isEqualTo:_userIdFromProvider).

  • I couldn’t access the context in this class to get the data from the Provider

  • Couldn’t send the userId to getCompaniesProfiles getter, as getter don’t take parameters

  • And if I convert this getter to a regular method, I wont be able to send the userID to it, as this has to run under void main() {runApp(MyApp());} / return MultiProvider(providers: [ and By then I cannot call fetch the sharedPreferences with a context that does not contain the provider info …

  • Couldn’t figure out how to receive the context as a constructor in this class, when I did, I got the following Only static members can accessed in initializers in class DatabaseService.

I’m still a beginner, so I would appreciate if you could share with me the best approach to handle this.
Thank you!

*********** Re-Edited by adding the below: **************
I’m trying to implement the same scenario, here is my code:
Main file:

return MultiProvider(
  providers: [
    ChangeNotifierProvider<SpData>(
      create: (context) => SpData(),
    ),
    ProxyProvider<SpData, DS>(
      create: (context) => DS(),
      update: (ctx, spData, previousDS) {
        print('ChangeNotifierProxyProvider RAN');
        previousDS.dbData = spData;
        return previousDS;
      },
    ),
  ],

SP File:

class SP {
  final String companyId;
  SP({
    @required this.companyId,
  });
}

class SpData with ChangeNotifier {
  var _sPData = SP(
    companyId: '',
  );
  void setCompanyId(String cpID) {
    final newSharedPreferences = SP(
      companyId: cpID,
    );
    _sPData = newSharedPreferences;
    print('_spData companyId:${_sPData.companyId}');
    notifyListeners();
  }

  String get getCompanyId {
    return _sPData.companyId;
  }
}

DS file:

class DS with ChangeNotifier {
  SpData dbData;
  void printCompanyId() {
    var companyId = dbData.getCompanyId;
    print('companyId from DataBase: $companyId');
  }
}

The SpData dbData; inside Class DS does not update. I’ve added the prints to figure out what is running and what is not. When I run my code, the print function in main.dart file print(‘ChangeNotifierProxyProvider RAN’); does not run.

What am I missing? Why ChangeNotifierProxyProvider is not being triggered, to update dbData inside DS file? Thanks!

Solution

You can use ProxyProvider for this purpose.

ProxyProvider is a provider that builds a value based on other providers.

You said you have a MultiProvider, so I guess you have SharedPreferencesData provider in this MultiProvider and then DatabaseService provider. What you need to do is use ProxyProvider for DatabaseService instead of a regular provider and base it on the SharedPreferencesData provider.

Here is an example:

MultiProvider(
  providers: [
    ChangeNotifierProvider<SharedPreferencesData>(
      create: (context) => SharedPreferencesData(),
    ),
    ProxyProvider<SharedPreferencesData, DatabaseService>(
      create: (context) => DatabaseService(),
      update: (context, sharedPreferencesData, databaseService) {
        databaseService.sharedPreferencesData = sharedPreferencesData;
        return databaseService;
      },
      dispose: (context, databaseService) => databaseService.dispose(),
    ),
  ],
  child: ...

Here is what happens in the code snippet above:

  1. ProxyProvider calls update everytime SharedPreferencesData changes.
  2. DatabaseService gets its sharedPreferencesData variable set inside update.

Now that you have access to sharedPreferencesData inside the DatabaseService instance, you can do what you want easily.

Answered By – Shahin Mursalov

Answer Checked By – David Marino (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published.