River_pod: The member 'state' can only be used within instance members of subclasses of 'package:state_notifier/state_notifier.dart'

Created on 16 Jul 2020  Â·  18Comments  Â·  Source: rrousselGit/river_pod

I follow source code from documentation :

Documentation example

class Counter extends StateNotifier<int> {
  Counter(): super(0);

  void increment() => state++;
}

final counterProvider = StateNotifierProvider((ref) => Counter());

// ...

@override
Widget build(BuildContext context) {
  // Obtains Counter without listening to its state.
  // Will not cause the button to rebuild when the counter changes.
  final Counter counter = useProvider(counterProvider);

  return RaisedButton(
    onPressed: () => counter.increment(),
    child: Text('increment'),
  );
}

Then i want custom it , when i click button i want show circularprogressindicator , after 3 second showing the text. So i already custom the example documentation to this :

ProviderLoading.dart

import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:state_notifier/state_notifier.dart';

class IsLoading extends StateNotifier<bool> {
  IsLoading() : super(false);
  void toggleLoading(bool value) => state = value;
}

final isLoadingProvider = StateNotifierProvider((ref) => IsLoading());

Main.dart

class TestingApp extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final IsLoading isLoading = useProvider(isLoadingProvider);
    return Scaffold(
      body: Container(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Center(
              child: isLoading.state ? CircularProgressIndicator() : Text('This Is Your Data'),
            ),
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: FlatButton(
                  onPressed: () => showLoading(isLoading), child: Text('Toggle Loading')),
            ),
          ],
        ),
      ),
    );
  }

  void showLoading(IsLoading isLoading) async {
    isLoading.toggleLoading(true);
    print("Proses");
    await Future.delayed(const Duration(seconds: 3));
    print("Done");
    isLoading.toggleLoading(false);
  }
}

But i got this warning and although i click button , it not showing the circularprogressindicator:

The member 'state' can only be used within instance members of subclasses of 'package:state_notifier/state_notifier.dart'.dart(invalid_use_of_protected_member)

I missed something ?

documentation

All 18 comments

As said by StateNotifierProvider, if you want to listen to the state of a StateNotifier, use "useProvider(myStateNotifierProvider.state)"

@rrousselGit but i still confused , i should make 2 variable to define state what i want listen and method from StateNotifier ?
something like :

final IsLoading isLoading = useProvider(isLoadingProvider); // it for method toggleLoading();
final bool loading = useProvider(isLoadingProvider.state); // it for listen state

Because documentation example i attach above , not showing to listen state and only show call method counter.increment()

Yes, that is correct.

Although the counter example does counter.read(context).increment()

It's more clear for me. But it's have option/possible make it more simple ? something like i only make 1 variable final loading = useProvider(loadingProvider) . and this variable can use for calling method and listen state ?

final loading = userProvider(loadingProvider);

//Listen state
Text(loading.state);

//Calling method 
loading.toggleLoading(value);

with above approach , will make warning like my thread . Because make 2 variable with almost same name , make me confused.

No this is expected

It allows reading the notifier without listening to the state, to avoid pointless rebuilds.

Mhm, I am trying to do exactly that (one for reading and one for methods), but I can't seem to get the method way to work?

final StateNotifierProvider themeProvider = StateNotifierProvider((ref) {
  return ThemeManager();
});

class ThemeManager extends StateNotifier<ThemeData> {
  ThemeManager() : super(darkTheme);

  void setTheme(ThemeData theme) async {
    state = theme;
  }
}

Then now when I try to obtain toggle setTheme, I retrieve it like so:

final ThemeManager themeManager = useProvider(themeProvider);

But I get error of:

The argument type 'StateNotifierProvider>' can't be assigned to the parameter type 'ProviderListenable'.

What am I doing wrong here?

EDIT: Nevermind, looks like I just had to remove the type annotation for:

- final StateNotifierProvider themeProvider = StateNotifierProvider((ref) {
+ final themeProvider = StateNotifierProvider((ref) {
  return ThemeManager();
});

As I mentioned, the syntax is "useProvider(themeProvider.state)"

@rrousselGit i don't know it's should create new issue or not , i got this warning again but in FutureProvider i want know this is best practice or not.

I have simple Http get request to show all user from server, and i using manage it using FutureProvider

Api

class UserGoogleApi {
  Future<List<UserGoogleModel>> getAllUser() async {
    final result = await reusableRequestServer.requestServer(() async {
      final response =
          await http.get('${appConfig.baseApiUrl}/${appConfig.userGoogleController}/getAllUser');
      final Map<String, dynamic> responseJson = json.decode(response.body);
      if (responseJson['status'] == 'ok') {
        final List list = responseJson['data'];
        final listUser = list.map((e) => UserGoogleModel.fromJson(e)).toList();
        return listUser;
      } else {
        throw responseJson['message'];
      }
    });
    return result;
  }
}

User Provider

class UserProvider extends StateNotifier<UserGoogleModel> {
  UserProvider([UserGoogleModel state]) : super(UserGoogleModel());

Future<UserGoogleModel> searchUserByIdOrEmail({
    String idUser,
    String emailuser,
    String idOrEmail = 'email_user',
  }) async {
    final result = await _userGoogleApi.getUserByIdOrEmail(
      idUser: idUser,
      emailUser: emailuser,
      idOrEmail: idOrEmail,
    );
    UserGoogleModel temp;
    for (var item in result) {
      temp = item;
    }
    state = UserGoogleModel(
      idUser: temp.idUser,
      createdDate: temp.createdDate,
      emailUser: temp.emailUser,
      imageUser: temp.emailUser,
      nameUser: temp.nameUser,
      tokenFcm: temp.tokenFcm,
      listUser: state.listUser,
    );

    return temp;
  }

  Future<List<UserGoogleModel>> showAllUser() async {
    final result = await _userGoogleApi.getAllUser();
    state.listUser = result;
    return result;
  }
}

final userProvider = StateNotifierProvider((ref) => UserProvider());

final showAllUser = FutureProvider.autoDispose((ref) async {
  final usrProvider = ref.read(userProvider);
  final result = await usrProvider.showAllUser();
  return result;
});

After that setup, i simply can call showAllUser like this :

   Consumer((ctx, read) {
              final provider = read(showAllUser);
              return provider.when(
                data: (value) {
                  return ListView.builder(
                    itemCount: value.length,
                    shrinkWrap: true,
                    itemBuilder: (BuildContext context, int index) {
                      final result = value[index];
                      return Text(result.nameUser);
                    },
                  );
                },
                loading: () => const CircularProgressIndicator(),
                error: (error, stackTrace) => Text('Error $error'),
              );
            }),

it's no problem if http request don't have required parameter, but i got problem if my http request required parameter. I don't know how to handle this.

Let's say , i have another http get to show specific user from id user or email user. Then API look like :

User Provider

final showSpecificUser = FutureProvider.autoDispose((ref) async {
  final usrProvider = ref.read(userProvider);

  final result = await usrProvider.searchUserByIdOrEmail(
    idOrEmail: 'id_user',
    idUser: usrProvider.state.idUser, //  => warning on "state"
  );
  return result;
});

When i access idUser from userProvider using usrProvider.state.idUser , i got this warning again The member 'state' can only be used within instance members of subclasses of 'package:state_notifier/state_notifier.dart'.

How can i get idUser from userProvider ? and i want know this is best practice using FutureProvider ?

Same answer as before: ref.read(userProvider.state)

On Wed, 22 Jul 2020, 03:56 Zeffry Reynando, notifications@github.com
wrote:

@rrousselGit https://github.com/rrousselGit i don't know it's should
create new issue or not , i got this warning again but in FutureProvider
i want know this is best practice or not.

I have simple Http get request to show all user from server, and i using
manage it using FutureProvider
Api

class UserGoogleApi {
Future> getAllUser() async {
final result = await reusableRequestServer.requestServer(() async {
final response =
await http.get('${appConfig.baseApiUrl}/${appConfig.userGoogleController}/getAllUser');
final Map responseJson = json.decode(response.body);
if (responseJson['status'] == 'ok') {
final List list = responseJson['data'];
final listUser = list.map((e) => UserGoogleModel.fromJson(e)).toList();
return listUser;
} else {
throw responseJson['message'];
}
});
return result;
}
}

User Provider

class UserProvider extends StateNotifier {
UserProvider([UserGoogleModel state]) : super(UserGoogleModel());

Future searchUserByIdOrEmail({
String idUser,
String emailuser,
String idOrEmail = 'email_user',
}) async {
final result = await _userGoogleApi.getUserByIdOrEmail(
idUser: idUser,
emailUser: emailuser,
idOrEmail: idOrEmail,
);
UserGoogleModel temp;
for (var item in result) {
temp = item;
}
state = UserGoogleModel(
idUser: temp.idUser,
createdDate: temp.createdDate,
emailUser: temp.emailUser,
imageUser: temp.emailUser,
nameUser: temp.nameUser,
tokenFcm: temp.tokenFcm,
listUser: state.listUser,
);

return temp;

}

Future> showAllUser() async {
final result = await _userGoogleApi.getAllUser();
state.listUser = result;
return result;
}
}

final userProvider = StateNotifierProvider((ref) => UserProvider());

final showAllUser = FutureProvider.autoDispose((ref) async {
final usrProvider = ref.read(userProvider);
final result = await usrProvider.showAllUser();
return result;
});

After that setup, i simply can call showAllUser like this :

Consumer((ctx, read) {
final provider = read(showAllUser);
return provider.when(
data: (value) {
return ListView.builder(
itemCount: value.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
final result = value[index];
return Text(result.nameUser);
},
);
},
loading: () => const CircularProgressIndicator(),
error: (error, stackTrace) => Text('Error $error'),
);
}),

it's no problem if http request don't have required parameter, but i got
problem if my http request required parameter. I don't know how to handle
this.

Let's say , i have another http get to show specific user from id user or
email user. Then API look like :
User Provider

final showSpecificUser = FutureProvider.autoDispose((ref) async {
final usrProvider = ref.read(userProvider);

final result = await usrProvider.searchUserByIdOrEmail(
idOrEmail: 'id_user',
idUser: usrProvider.state.idUser, // => warning on "state"
);
return result;
});

When i access idUser from userProvider using usrProvider.state.idUser , i
got this warning again The member 'state' can only be used within
instance members of subclasses of
'package:state_notifier/state_notifier.dart'.

How can i get idUser from userProvider ? and i want know this is best
practice using FutureProvider ?

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rrousselGit/river_pod/issues/52#issuecomment-662212619,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AEZ3I3IMSIXLCQMA64GQ4Z3R4ZIPHANCNFSM4O4FKPVA
.

@rrousselGit ref.read(userProvider.state); give me error :

The argument type 'StateNotifierStateProvider<UserGoogleModel>' can't be assigned to the parameter type 'ProviderBase<ProviderDependency<dynamic>, Object>'.

I think method read on Consumer and FutureProvider it's different.

read on Consumer accept Res Function<Res>(ProviderBase<ProviderDependencyBase, Res>)
read on FutureProviduer accept ProviderBase<ProviderDependency<T>, Object>

Oh my bad, that's for the next changes

For now you can do ref.read(userProvider).addListener((state) {...}

@rrousselGit i have 2 problem with this approach .

  1. Value obtained from listener always null , although i'm sure it's have value.

UserProvider

final showSpecificUser = FutureProvider.autoDispose((ref) async {
  UserGoogleModel temp;
  final usrProvider = ref.read(userProvider);
  usrProvider.addListener((state) async {
    final result = await usrProvider.searchUserByIdOrEmail(emailuser: state.emailUser);
    // print('listenern RESULT ${result.nameUser} || ${result.emailUser} || ${result.idUser}');
    temp = result;
    print('Temp RESULT ${result.nameUser} || ${result.emailUser} || ${result.idUser}');
  });

  return temp;
});

And i simple call it like this

     Consumer((ctx, read) {
              final provider = read(showSpecificUser);
              return provider.when(
                data: (value) {
                  if (value == null) {
                    return Text('Not have data');
                  }
                  // print('specific user ${value.nameUser}');
                  return Text(value.nameUser ?? 'null');
                },
                loading: () => const CircularProgressIndicator(),
                error: (error, stackTrace) => Text(error.toString()),
              );
            }),

Temp Result

tanya rousel

tanya rousel 2

2.With addListener((state)async{}) it will always listen ,although i already used FutureProvider.autoDisposed.

Although already used FutureProvider.autoDisposed , after logout it's still listen old value. please see below gif.
tanya rousel 3

Can't you just add a getter for state in your class that extends StateNotifier? Assuming that your FutureProvider is being properly disposed after each use that should be a suitable workaround until the new changes to Riverpod are live. I did a quick test to see and it does work. Make sure you define a getter like this and don't override the default defined by StateNotifier.

class A extends StateNotifier<B> {
...
  static final provider = StateNotifierProvider((ref) => A());

  getState() => state;
...
}
final provider = FutureProvider.autoDispose((ref) async {
    final a = ref.read(A.provider);
    final t = a.getState();
    print(t);
});

Not ideal but seems like a fine workaround. I believe the intention of state being inaccessible outside is to ensure state manipulations are handled by the StateNotifier itself, so using a getter in the meantime wouldn't be the end of the world.

@AlexHartford interesting, i will try it .

Not necessarily a good practice, as the value may change over time.
You may want a StreamProvider for that reason too

@rrousselGit based on my above code , what's the problem with my code ?

If "t" changes,this will not print the new value

@rrousselGit I think best option it's waiting you implement what you mention on it

Was this page helpful?
0 / 5 - 0 ratings