Flutter GetX Provider & GetConnect Settings - flutter

I created a Flutter project with GetX CLI. Then i created a provider with get generate model. But i have problems with provider:
When i bind it with lazyPut, onInit() never works.
When i bind it with put, onInit() works but httpClient.defaultDecoder section does
not executed.
When i bind it with put, onInit() works and httpClient.baseUrl is adjusted but i can't read it in getFaculties. It seems like null in global.
When i put httpClient.defaultDecoder and httpClient.baseUrl in getFaculties, works well. But doesn't seem like the right way.
Am i doing something wrong or is it about GetConnect? Thanks in advance.
Provider:
class FacultyProvider extends GetConnect {
#override
void onInit() {
print('PROVIDER INIT!'); // Works with Get.put(FacultyProvider()) in Bindings
httpClient.defaultDecoder = (map) { // NEVER WORKS
if (map is Map<String, dynamic>) {
print('MAP!');
return Faculty.fromJson(map);
}
if (map is List) {
print('LIST!');
return map.map((item) => Faculty.fromJson(item)).toList();
}
};
httpClient.baseUrl = 'http://localhost:3000/public';
}
Future<List<Faculty>> getFaculties(bool cache) async {
print(httpClient.baseUrl); // ALWAYS NULL
final response = await httpClient.get('/faculties/true'); // NO HOST ERROR
return response.body; // TYPE ERRORS because of defaultDecoder section didn't run
}
Bindings:
class FacultiesBinding extends Bindings {
#override
void dependencies() {
Get.put(FacultyProvider());
// Get.lazyPut<FacultyProvider>(() => FacultyProvider());
Get.lazyPut<FacultiesController>(() => FacultiesController());
}

Related

Error on generated file for AsyncNotifierProvider `.family` Riverpod providers

I wonder if I am overlooking something. When ever I try to generate the following via riverpod_annotation I'm getting the error below where it cannot find Family class. I'm pretty sure I'm doing something wrong, but I'm not sure what.
I've deleted and rebuilt the file multiple times and I'm not sure what I can change to make it work.
Here's the gist with both the controller and the generated controller logic
https://gist.github.com/Morzaram/7d75bcfed06ea7cce88a8b11c4fad223
import 'package:front_end/utils/pocketbase_provider.dart';
import 'package:pocketbase/pocketbase.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'mangage_topic_voices_controller.g.dart';
#riverpod
class ManageTopicVoicesController extends _$ManageTopicVoicesController {
List<String> _selectedVoices = [];
bool mounted = true;
get selectedVoices => _selectedVoices;
#override
FutureOr<void> build({required List<String> ids}) {
ref.onDispose(() {
mounted = false;
});
if (mounted) {
_selectedVoices = ids;
}
}
void addVoice(String id) {
_selectedVoices = [..._selectedVoices, id];
}
void removeVoice(String id) {
_selectedVoices = _selectedVoices.where((e) => e != id).toList();
}
Future<RecordModel> updateTopic({topicId, selectedVoices}) async {
final res = await pb
.collection('topics')
.update(topicId, body: {"voices": selectedVoices});
return res;
}
}
The error I'm getting is Classes can only extend other classes. Try specifying a different superclass, or removing the extends clause. and it's occuring on the first line of Family<AsyncValue<void>>
class ManageTopicVoicesControllerFamily extends Family<AsyncValue<void>> {
ManageTopicVoicesControllerFamily();
ManageTopicVoicesControllerProvider call({
required List<String> ids,
}) {
return ManageTopicVoicesControllerProvider(
ids: ids,
);
}
#override
AutoDisposeAsyncNotifierProviderImpl<ManageTopicVoicesController, void>
getProviderOverride(
covariant ManageTopicVoicesControllerProvider provider,
) {
return call(
ids: provider.ids,
);
}
#override
List<ProviderOrFamily>? get allTransitiveDependencies => null;
#override
List<ProviderOrFamily>? get dependencies => null;
#override
String? get name => r'manageTopicVoicesControllerProvider';
}
I know that the error is saying that the Family class doesn't exist, but I'm not sure if the error is due to me or not.
Can I not use family with this currently? I would love any help that I can get.
I'm new to dart, so apologies, and thank you in advance!
Here's the gist with both files

Flutter Provider: Should I create a new Provider for every class?

Lets say I have multiple settings a user can set. Should I have one Provider which manages all settings like so:
class Settings with ChangeNotifier {
SettingsA _settingsA;
SettingsB _settingsB;
List<String> _settingsC;
SettingsA get settingsA => _settingsA;
SettingsB get settingsB => _settingsB;
List<String> get settingsC => _settingsC;
// Setters
void updateA(SettingsA settingsA) {
_settingsA = settingsA;
notifyListeners();
}
void updateB(SettingsB settingsB) {
_settingsB = settingsB;
notifyListeners();
}
void addToC(String setting) {
_settingsC.add(setting);
notifyListeners();
}
}
Or should I rather make a Provider for every object like so:
class SettingsAProvider with ChangeNotifier {
SettingsA _settingsA;
SettingsA get settingsA => _settingsA;
// Setters
void update(SettingsA settingsA) {
_settingsA = settingsA;
notifyListeners();
}
}
What is the best practise of using ChangeNotifierProviders?
In my opinion, you should use SettingAProvider,SettingBProvider,...
If you use Settings Class...
When you call updateA, it will notify all value _settingA,_settingB,_settingC,... even if unecessary.

Mocking GetStorage for testing in flutter

I am unit testing and widget testing my code. I have tried mokito and moktail to mock the Get storage but get this error:
package:get_storage/src/storage_impl.dart 47:7 GetStorage._init
===== asynchronous gap ===========================
package:get_storage/src/storage_impl.dart 28:7 new GetStorage._internal.<fn>
the class that I am testing:
class ShowCaseController extends GetxController {
final box = GetStorage();
displayAnySC(String playKey, String replayKey, GetStorage box) async {
bool? showcasePlayStatus = box.read(playKey);
if (showcasePlayStatus == null) {
box.write(playKey, false);
// box.remove(replayKey);
box.write(replayKey, false);
return true;
}
bool? showcaseReplayStatus = box.read(replayKey);
if (showcaseReplayStatus == null) {
box.write(replayKey, false);
return true;
}
return false;
}
}
here is one empty simple test using mock that gives error:
class MockStorage extends Mock implements GetStorage {}
void main() {
group('Show case controller', () {
final showCaseCnt = ShowCaseController();
late bool _data;
late MockStorage _mockStorage;
setUp(() {
_mockStorage = MockStorage();
_data = showCaseCnt.displayAnySC('playKey', 'replayKey', _mockStorage);
});
test(
'displayAnySC should return false when the play and replay are not null',
() {
});
});
}
I have tried #GenerateMocks([ShowCaseController]) and also added GetStorage.init() inside the main function of the test but got the same error.
P.S. I haven't seen any article or question related to mocking the GetStorage for test in Flutter. Appreciate any explanation or link that helps in this regard.
I am using the GetX package for dependency injection and state management. And using the GetStorage package for keeping the theme persistent and storing keys for notifying the app to play or replay ShowCaseView.

Flutter GetX state management initial null value

This is what I'm trying to achieve using flutter GetX package but not working properly.
I have a Firestore document, if the document is changed I want to call an api and keep the data up to date as observable.
The code below seems to work but initial screen shows null error then it shows the data.
I don't know how I can make sure both fetchFirestoreUser() and fetchApiData() (async methods) returns data before I move to the home screen.
GetX StateMixin seems to help with async data load problem but then I don't know how I can refresh the api data when the firestore document is changed.
I'm not sure if any other state management would be best for my scenario but I find GetX easy compared to other state management package.
I would very much appreciate if someone would tell me how I can solve this problem, many thanks in advance.
Auth Controller.
class AuthController extends SuperController {
static AuthController instance = Get.find();
late Rx<User?> _user;
FirebaseAuth auth = FirebaseAuth.instance;
var _firestoreUser = FirestoreUser().obs;
var _apiData = ProfileUser().obs;
#override
void onReady() async {
super.onReady();
_user = Rx<User?>(auth.currentUser);
_user.bindStream(auth.userChanges());
//get firestore document
fetchFirestoreUser();
//fetch data from api
fetchApiData();
ever(_user, _initialScreen);
//Refresh api data if firestore document has changed.
_firestoreUser.listen((val) {
fetchApiData();
});
}
Rx<FirestoreUser?> get firestoreUser => _firestoreUser;
_initialScreen(User? user) {
if (user == null) {
Get.offAll(() => Login());
} else {
Get.offAll(() => Home());
}
}
ProfileUser get apiData => _apiData.value;
void fetchFirestoreUser() async {
Stream<FirestoreUser> firestoreUser =
FirestoreDB().getFirestoreUser(_user.value!.uid);
_firestoreUser.bindStream(firestoreUser);
}
fetchApiData() async {
var result = await RemoteService.getProfile(_user.value!.uid);
if (result != null) {
_apiData.value = result;
}
}
#override
void onDetached() {}
#override
void onInactive() {}
#override
void onPaused() {}
#override
void onResumed() {
fetchApiData();
}
}
Home screen
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: Obx(() =>
Text("username: " + AuthController.instance.apiData.username!))),
),
);
}
}
To be honest, I never used GetX so I'm not too familiar with that syntax.
But I can see from your code that you're setting some mutable state when you call this method:
fetchApiData() async {
var result = await RemoteService.getProfile(_user.value!.uid);
if (result != null) {
_apiData.value = result;
}
}
Instead, a more robust solution would be to make everything reactive and immutable. You could do this by combining providers if you use Riverpod:
final authStateChangesProvider = StreamProvider.autoDispose<User?>((ref) {
final authService = ref.watch(authRepositoryProvider);
return authService.authStateChanges();
});
final apiDataProvider = FutureProvider.autoDispose<APIData?>((ref) {
final userValue = ref.watch(authStateChangesProvider);
final user = userValue.value;
if (user != null) {
// note: this should also be turned into a provider, rather than using a static method
return RemoteService.getProfile(user.uid);
} else {
// decide if it makes sense to return null or throw and exception when the user is not signed in
return Future.value(null);
}
});
Then, you can just use a ConsumerWidget to watch the data:
#override
Widget build(BuildContext context, WidgetRef ref) {
// this will cause the widget to rebuild whenever the auth state changes
final apiData = ref.watch(apiDataProvider);
return apiData.when(
data: (data) => /* some widget */,
loading: () => /* some loading widget */,
error: (e, st) => /* some error widget */,
);
}
Note: Riverpod has a bit of a learning curve (worth it imho) so you'll have to learn it how to use it first, before you can understand how this code works.
Actually the reason behind this that you put your controller in the same page that you are calling so in the starting stage of your page Get.put() calls your controller and because you are fetching data from the API it takes a few seconds/milliseconds to get the data and for that time your Obx() renders the error. To prevent this you can apply some conditional logic to your code like below :
Obx(() => AuthController.instance.apiData != null ? Text("username: " + AuthController.instance.apiData.username!) : CircularProgressIndicator())) :

how can I get the other controller's variable inside one controller in flutter using getx

This is an issue related to the getx in flutter.
I have 2 controllers. ContractsController and NotificationController.
In ContractsController I have put the value into observer variable by calling the Api request.
What I want now is to get that variable's data in another controller - NotificationController.
How to get that value using getx functions?
ContractsController
class ContractsController extends GetxController {
ExpiringContractRepository _expiringContractRepository;
final expiringContracts = <ExpiringContract>[].obs; // This is the value what I want in another controller
ContractsController() {
_expiringContractRepository = new ExpiringContractRepository();
}
#override
Future<void> onInit() async {
await refreshContracts();
super.onInit();
}
Future refreshContracts({bool showMessage}) async {
await getExpiringContracts();
if (showMessage == true) {
Get.showSnackbar(Ui.SuccessSnackBar(message: "List of expiring contracts refreshed successfully".tr));
}
}
Future getExpiringContracts() async {
try {
expiringContracts.value = await _expiringContractRepository.getAll(); // put the value from the api
} catch (e) {
Get.showSnackbar(Ui.ErrorSnackBar(message: e.toString()));
}
}
}
The expiringContracts is updated successfully with data after the api request.
Now, I want to get that value in NotificationController
NotificationController
class NotificationsController extends GetxController {
final notifications = <Notification>[].obs;
ContractsController contractsController;
NotificationsController() {
}
#override
void onInit() async {
contractsController = Get.find<ContractsController>();
print(contractsController.expiringContracts); // This shows an empty list ?????
super.onInit();
}
}
Overview
A couple solutions come to mind:
pass the expiringContracts list as a constructor argument to NotificationsController if you only need this done once at instantiation, or
use a GetX worker to update NotificationsController every time expiringContracts is updated
The first solution isn't related to GetX, rather it's just async coordination between ContractsController and NotificationsController, so lets focus on the 2nd solution: GetX Workers.
Details
In NotificationsController, create a method that will receive expiringContracts.
Something like:
class NotificationsController extends GetxController {
void refreshContracts(List<ExpiringContract> contracts) {
// do something
}
}
Please note: none of this code is tested. I'm writing this purely in StackOverflow, so consider this pseudo-code.
In ContractsController we'll supply the above callback method as a constructor arg:
In ContractsController, something like:
class ContractsController {
final expiringContracts = <ExpiringContract>[].obs
final Function(List<ExpiringContract>) refreshContractsCallback;
ContractsController(this.refreshContractsCallback);
#override
void onInit() {
super.onInit();
refreshContracts(); // do your stuff after super.onInit
ever(expiringContracts, refreshContractsCallback);
// ↑ contracts → refreshContractsCallback(contracts)
// when expiringContracts updates, run callback with them
}
}
Here the GetX ever worker takes the observable as first argument, and a function as 2nd argument. That function must take an argument of type that matches the observed variable, i.e. List<ExpiringContract>, hence the Type of refreshContractsCallback was defined as Function(List<ExpiringContract>).
Now whenever the observable expiringContracts is updated in ContractsController, refreshContractsCallback(contracts) will be called, which supplies the list of expiring contracts to NotificationsController via refreshContracts.
Finally, when instantiating the two controllers inside the build() method of your route/page:
NotificationsController nx = Get.put(NotificationsController());
ContractsController cx = Get.put(ContractsController(nx.refreshContracts));
Timeline of Events
NotificationsController gets created as nx.
nx.onInit() runs, slow call of refreshContracts() starts
ContractsController gets created, with nx.refreshContracts callback
your page paints
nx has no contracts data at this point, so you'll prob. need a FutureBuilder or an Obx/ GetX + StatelessWidget that'll rebuild when data eventually arrives
when refreshContracts() finishes, ever worker runs, sending contracts to nx
nx.refreshContracts(contracts) is run, doing something with contracts
Notes
async/await was removed from nx.onInit
ever worker will run when refreshContract finishes
There were some powerful approaches in GetX. I solved this issue with Get.put and Get.find
Here is the code that I added.
ContractsController
class ContractsController extends GetxController {
ExpiringContractRepository _expiringContractRepository;
final expiringContracts = <ExpiringContract>[].obs; // This is the value what I want in another controller
ContractsController() {
_expiringContractRepository = new ExpiringContractRepository();
}
#override
Future<void> onInit() async {
await refreshContracts();
super.onInit();
}
Future refreshContracts({bool showMessage}) async {
await getExpiringContracts();
if (showMessage == true) {
Get.showSnackbar(Ui.SuccessSnackBar(message: "List of expiring contracts refreshed successfully".tr));
}
}
Future getExpiringContracts() async {
try {
expiringContracts.value = await _expiringContractRepository.getAll(); // put the value from the API
// ******************************** //
Get.put(ContractsController()); // Added here
} catch (e) {
Get.showSnackbar(Ui.ErrorSnackBar(message: e.toString()));
}
}
}
NotificationController
class NotificationsController extends GetxController {
final notifications = <Notification>[].obs;
ContractsController contractsController;
NotificationsController() {
}
#override
void onInit() async {
// ******************************** //
contractsController = Get.find<ContractsController>(); // Added here.
print(contractsController.expiringContracts); // This shows the updated value
super.onInit();
}
}
Finally, I have found that GetX is simple but powerful for state management in flutter.
Thanks.