Can't initialized GraphQl Client in flutter using Get_it - flutter

I want to implement GraphQL client in my flutter app. For Dependency injection, I use GetIt library. But when I run the app, it says
'Invalid argument (Object of type HomeGraphQLService is not
registered inside GetIt. Did you forget to pass an instance name?
(Did you accidentally do GetIt sl=GetIt.instance(); instead of GetIt
sl=GetIt.instance;)): HomeGraphQLService'
.
It means GraphQL client did not instantiate somehow, although I registered it in my service locator
Session.dart
abstract class Session {
String getAccessToken();
}
SessionImpl.dart
class SessionImpl extends Session {
SharedPreferences sharedPref;
SessionImpl(SharedPreferences sharedPref) {
this.sharedPref = sharedPref;
}
#override
String getAccessToken() {
return sharedPref.getString('access_token') ?? "";
}
}
GraphQLClientGenerator.dart
class GraphQLClientGenerator {
Session session;
GraphQLClientGenerator(Session session) {
this.session = session;
}
GraphQLClient getClient() {
final HttpLink httpLink = HttpLink('https://xxx/graphql');
final AuthLink authLink = AuthLink(getToken: () async => 'Bearer ${_getAccessToken()}');
final Link link = authLink.concat(httpLink);
return GraphQLClient(link: link, cache: GraphQLCache(store: InMemoryStore()));
}
String _getAccessToken() {
return session.getAccessToken();
}
}
HomeRepository.dart
abstract class HomeRepository {
Future<List<Course>> getAllCourseOf(String className, String groupName);
}
HomeRepositoryImpl.dart
class HomeRepositoryImpl extends HomeRepository {
HomeGraphQLService homeGraphQLService;
HomeMapper homeMapper;
HomeRepositoryImpl(HomeGraphQLService homeGraphQLService, HomeMapper homeMapper) {
this.homeGraphQLService = homeGraphQLService;
this.homeMapper = homeMapper;
}
#override
Future<List<Course>> getAllCourseOf(String className, String groupName) async {
final response = await homeGraphQLService.getAllCourseOf(className, groupName);
return homeMapper.toCourses(response).where((course) => course.isAvailable);
}
}
HomeGraphQLService.dart
class HomeGraphQLService {
GraphQLClient graphQLClient;
HomeGraphQLService(GraphQLClient graphQLClient) {
this.graphQLClient = graphQLClient;
}
Future<SubjectResponse> getAllCourseOf(String className, String groupName) async {
try {
final response = await graphQLClient.query(getAllCourseQuery(className, groupName));
return SubjectResponse.fromJson((response.data));
} catch (e) {
return Future.error(e);
}
}
}
GraphQuery.dart
QueryOptions getAllCourseQuery(String className, String groupName) {
String query = """
query GetSubject($className: String, $groupName: String) {
subjects(class: $className, group: $groupName) {
code
display
insights {
coming_soon
purchased
}
}
}
""";
return QueryOptions(
document: gql(query),
variables: <String, dynamic>{
'className': className,
'groupName': groupName,
},
);
}
ServiceLocator.dart
final serviceLocator = GetIt.instance;
Future<void> initDependencies() async {
await _initSharedPref();
_initSession();
_initGraphQLClient();
_initGraphQLService();
_initMapper();
_initRepository();
}
Future<void> _initSharedPref() async {
SharedPreferences sharedPref = await SharedPreferences.getInstance();
serviceLocator.registerSingleton<SharedPreferences>(sharedPref);
}
void _initSession() {
serviceLocator.registerLazySingleton<Session>(()=>SessionImpl(serviceLocator()));
}
void _initGraphQLClient() {
serviceLocator.registerLazySingleton<GraphQLClient>(() => GraphQLClientGenerator(serviceLocator()).getClient());
}
void _initGraphQLService() {
serviceLocator.registerLazySingleton<HomeGraphQLService>(() => HomeGraphQLService(serviceLocator()));
}
void _initMapper() {
serviceLocator.registerLazySingleton<HomeMapper>(() => HomeMapper());
}
void _initRepository() {
serviceLocator.registerLazySingleton<HomeRepository>(() => HomeRepositoryImpl(serviceLocator(), serviceLocator()));
}
main.dart
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown],
);
await initDependencies();
runApp(MyApp());
}

I cannot say where exactly it is happening because it is elsewhere in your code where you are accessing the GraphQLService, but the problem is definitely due to the lazy loading. The object has not been created and loaded by the locator before it is being accessed. Try updating ServiceLocator.dart to instantiate the classes during registration, like so:
void _initSession() {
serviceLocator.registerSingleton<Session>.(SessionImpl(serviceLocator()));
}
void _initGraphQLClient() {
serviceLocator.registerSingleton<GraphQLClient>(
GraphQLClientGenerator(serviceLocator()).getClient());
}
void _initGraphQLService() {
serviceLocator.registerSingleton<HomeGraphQLService>(
HomeGraphQLService(serviceLocator()));
}
void _initMapper() {
serviceLocator.registerSingleton<HomeMapper>(HomeMapper());
}
void _initRepository() {
serviceLocator.registerSingleton<HomeRepository>(
HomeRepositoryImpl(serviceLocator(), serviceLocator()));
}

Related

How do I listen to two lists within a class in Flutter riverpod?

class QuestionPaperController extends StateNotifier<List<String>> {
QuestionPaperController() : super([]);
Future<void> getAllPapers(WidgetRef ref) async {
List<String> imgName = ["biology", "chemistry", "maths", "physics"];
try {
for (var img in imgName) {
final imgUrl = await ref.read(firebaseStorageProvider).getImage(img);
state = [...state, imgUrl!];
}
} catch (e) {
print(e);
}
}
}
final questionPaperControllerProvider =
StateNotifierProvider<QuestionPaperController, List<String>>((ref) {
return QuestionPaperController();
});
I want to add another list that its name will stackoverflow for this class and watch it but statenotifier listening another list what can I do?
You need to create another instance of the class
class StackoverflowController extends StateNotifier<List<String>> {
/// ...
}
final stackoverflowControllerProvider =
StateNotifierProvider<StackoverflowController, List<String>>((ref) {
return StackoverflowController();
});
and create provider that watch the other two
final otherProvider = Provider<...>((ref) {
ref.watch(stackoverflowControllerProvider);
ref.watch(questionPaperControllerProvider );
return ...;
});
bonus: you can pass ref in class-controller:
final fizzControllerPr = Provider.autoDispose((ref) => FizzController(ref));
// or use tear-off
final fizzControllerPr1 = Provider.autoDispose(FizzController.new);
/// Class represent controller.
class FizzController {
FizzController(this._ref);
final Ref _ref;
Future<void> getAllPapers() async {
//...
final imgUrl = await _ref.read(firebaseStorageProvider).getImage(img);
//...
}
}

how to mock the state of a StateNotifierProvider flutter

my test is throwing an exception because there is a StateNotifierProvider inside which is not overridden. for a regular Provider, i can override it using providerContainer, but for the state of a stateNotifierProvider, I don't know how to do it. I tried my best but I reached the limit of my best. I already saw this and this but it didn't help.
Appreciate much if someone could help me out of this. Thanks
My service File
class ReportService {
final Ref ref;
ReportService({
required this.ref,
});
Future<void> testReport() async {
//* How can i override this provider ?
final connection = ref.read(connectivityServiceProvider);
if (connection) {
try {
await ref.read(reportRepositoryProvider).testFunction();
} on FirebaseException catch (e, st) {
ref.read(errorLoggerProvider).logError(e, st);
throw Exception(e.message);
}
} else {
throw Exception('Check internet connection...');
}
}
}
final reportServiceProvider = Provider<ReportService>((ref) => ReportService(
ref: ref,
));
My test file
void main() {
WidgetsFlutterBinding.ensureInitialized();
final reportRepository = MockReportRepository();
ReportService makeReportService() {
final container = ProviderContainer(overrides: [
reportRepositoryProvider.overrideWithValue(reportRepository),
]);
addTearDown(container.dispose);
return container.read(reportServiceProvider);
}
test('test test', () async {
//How to stub the connectivityServiceProvider here ?
when(reportRepository.testFunction)
.thenAnswer((invocation) => Future.value());
final service = makeReportService();
await service.testReport();
verify(reportRepository.testFunction).called(1);
});
My StateNotifierProvider
class ConnectivityService extends StateNotifier<bool> {
ConnectivityService() : super(false);
}
final connectivityServiceProvider =
StateNotifierProvider<ConnectivityService, bool>(
(ref) => ConnectivityService());

How to fix LateInitializationError about non-nullable variables?

I was studying with an AWS tutorial however, that tutorial is not null safety.
I'v tried to convert it, but is showing:
LateInitializationError: Field '_credentials#26120019' has not been initialized.
I think that "late" modifier is not initializing the variables when I try to get the values in verifyCode
Pleease, how can I fix the code below
enum AuthFlowStatus {login, signUp, verification, session}
class AuthState {
final AuthFlowStatus? authFlowStatus;
AuthState({this.authFlowStatus});
}
class AuthService {
final authStateController = StreamController<AuthState>();
late AuthCredentials _credentials;
void showSignUp() {
final state = AuthState(authFlowStatus: AuthFlowStatus.signUp);
authStateController.add(state);
}
void showLogin() {
final state = AuthState(authFlowStatus: AuthFlowStatus.login);
authStateController.add(state);
}
void loginWithCredentials(AuthCredentials credentials) async {
try {
final result = await Amplify.Auth.signIn(
username: credentials.username, password: credentials.password,
);
if (result.isSignedIn) {
final state = AuthState(authFlowStatus: AuthFlowStatus.session);
authStateController.add(state);
} else {
print('User could not be signed in');
}
} on AuthException catch (authError) {
print('Could not login - ${authError}');
}
}
void signUpWithCredentials(SignUpCredentials credentials) async {
try {
Map<CognitoUserAttributeKey, String> userAttributes = {
CognitoUserAttributeKey.email: credentials.email,
};
final result = await Amplify.Auth.signUp(
username: credentials.username,
password: credentials.password,
options: CognitoSignUpOptions(
userAttributes: userAttributes
),
);
if (result.isSignUpComplete) {
loginWithCredentials(credentials);
} else {
this._credentials = credentials;
}
final state = AuthState(authFlowStatus: AuthFlowStatus.verification);
authStateController.add(state);
} on AmplifyException catch (authError) {
print('Failed ro sign up - ${authError}');
}
}
void verifyCode(String verificationCode) async {
try {
final result = await Amplify.Auth.confirmSignUp(
username: _credentials.username,
confirmationCode: verificationCode,
);
if (result.isSignUpComplete) {
loginWithCredentials(_credentials);
} else {
//not implemented yet
}
} on AuthException catch (authError) {
print('Could not verify code - ${authError}');
}
}
}

Flutter: Shared preferences singleton not initializing properly

I'm very new to Flutter and Dart, comming from android, bringing some of my habbits with me, I want to implement a SharedPreferences singleton object to simplify and avoid repetition (duplication).
this is my SharedPreferences singleton class:
import 'package:shared_preferences/shared_preferences.dart';
import 'package:synchronized/synchronized.dart';
class MySharedPreferences {
static MySharedPreferences _instance;
SharedPreferences _preferences;
// keys
final String _logged = "LOGGED";
final String _accessToken = "ACCESS_TOKEN";
MySharedPreferences._() {
_initSharedPreferences();
}
static MySharedPreferences getInstance() {
var lock = new Lock();
if (_instance == null) {
lock.synchronized(() => {_instance = new MySharedPreferences._()});
return _instance;
} else
return _instance;
}
_initSharedPreferences() async {
_preferences = await SharedPreferences.getInstance();
}
bool checkLogged() {
return _preferences.getBool(_logged);
}
void setLogged(bool logged) {
_preferences.setBool(_logged, logged);
}
well most of this logic is what i used to do in android, and used to work perfectly, but when i tried testing it, the singleton is always null, here is the test:
import 'package:flutter_test/flutter_test.dart';
import 'package:reportingsystem/local/my_shared_preferences.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
test('Test the shared_preferences', () {
MySharedPreferences preferences = MySharedPreferences.getInstance();
preferences.setLogged(true);
expect(preferences.checkLogged(), true);
preferences.setLogged(false);
expect(preferences.checkLogged(), false);
});
}
The test fails because the "preferences" object is null, i don't know what wrong, and i don't find much about it in the docs.
here is the stacktrace:
dart:core Object.noSuchMethod
package:reportingsystem/local/my_shared_preferences.dart 34:18 MySharedPreferences.setLogged
test\shared_preferences_test.dart 8:17 main.<fn>
test\shared_preferences_test.dart 6:39 main.<fn>
NoSuchMethodError: The method 'setBool' was called on null.
Receiver: null
Tried calling: setBool("LOGGED", true)
Here's an example where you must call init when first calling the Singleton, and then you'll be able to access it synchronously.
class MySharedPreferences {
static final MySharedPreferences _instance = MySharedPreferences._internal();
MockSharedPreferences prefereces;
factory MySharedPreferences() {
return _instance;
}
Future<void> init() async {
if (prefereces != null) {
return;
}
prefereces = await Future.delayed(Duration(seconds: 1), () => MockSharedPreferences());
}
MySharedPreferences._internal();
}
class MockSharedPreferences {
final Map<String, bool> data = {};
void setBool(String key, bool value) {
data[key] = value;
print('data $data');
}
}
Then you can use it without await after first initialization, like this:
Future<void> main() async {
await first();
anyOther();
}
void anyOther() {
MySharedPreferences singleton = MySharedPreferences();
singleton.prefereces.setBool('first', true);
}
Future<void> first() async {
MySharedPreferences singleton = MySharedPreferences();
await singleton.init();
singleton.prefereces.setBool('notFirst', true);
}

Handling Errors with Dio/bloc Flutter

I need a good way to handle errors while I'm Using Dio requests.
Can I do it in one class and pass the dio request throw it ?
and it should return a response with the error .
I am posting my generalized network bloc here, which can be reused any number of time, any where. also, It uses dio using API repository , exceptional and error handling.
class NetworkBloc extends Bloc<NetworkEvent, NetworkState> {
NetworkBloc() : super(NetworkRequestInitial());
#override
Stream<NetworkState> mapEventToState(
NetworkEvent event,
) async* {
yield NetworkRequestInitiated();
if (event is NetworkCallEvent) {
RequestType requestType = event.requestType;
if (requestType == RequestType.GET) {
yield* fetchData(event);
} else if (requestType == RequestType.POST) {
yield* uploadDataAndStoreResult(event);
}
}
}
Stream<NetworkState> fetchData(NetworkCallEvent event) async* {
Response response;
try {
yield NetworkRequestLoading();
response =
await event.apiRepository.sendGetRequest(event.url, event.request);
if (response.statusCode == 200) {
yield NetworkRequestLoaded(response: response);
} else {
Map jsonResponse = jsonDecode(response.data);
yield NetworkRequestFailure(message: jsonResponse['message']);
}
} catch (e) {
yield NetworkRequestFailure(
message: NetworkUtils.getErrorMessageAccordingToError(e));
}
}
Stream<NetworkState> uploadDataAndStoreResult(NetworkCallEvent event) async* {
Response response;
try {
yield NetworkRequestLoading();
if (event.request != null) {
if (event.isHeadersNeeded) {
response = await event.apiRepository.sendPostRequestWithHeader(
event.url,
request: event.request,
);
} else {
response = await event.apiRepository.sendPostRequest(
event.url,
event.request,
);
}
} else {
response = await event.apiRepository
.sendPostRequestWithoutBodyParameters(event.url);
}
if (response.statusCode == 200) {
saveDataAccordingToCacheMechanism(event, response);
yield NetworkRequestLoaded(response: response);
} else {
Map jsonResponse = jsonDecode(response.data);
yield NetworkRequestFailure(message: jsonResponse['message']);
}
} catch (e) {
yield NetworkRequestFailure(
message: NetworkUtils.getErrorMessageAccordingToError(e));
}
}
void saveDataAccordingToCacheMechanism(
NetworkCallEvent event, Response response) async {
if (event.cacheMechanism == CacheMechanism.SharePreferences) {
Hive.box(ConstUtils.dbName)
.put(event.keyForSharedPreferences, response.data.toString());
} else if (event.cacheMechanism == CacheMechanism.Database) {}
}
}
I am also adding states and events to make it more easy to understand.
class NetworkCallEvent extends NetworkEvent {
final String request;
final dynamic url;
final RequestType requestType;
final CacheMechanism cacheMechanism;
final String keyForSharedPreferences;
final APIRepository apiRepository;
final bool isHeadersNeeded;
NetworkCallEvent(
{#required this.url,
this.request,
this.isHeadersNeeded = false,
#required this.requestType,
#required this.apiRepository,
#required this.cacheMechanism,
this.keyForSharedPreferences});
#override
List<Object> get props => [
this.url,
this.request,
this.requestType,
this.cacheMechanism,
this.keyForSharedPreferences,
this.apiRepository
];
}
Network_states:
class NetworkRequestInitial extends NetworkState {}
class NetworkRequestInitiated extends NetworkState {}
class NetworkRequestLoading extends NetworkState {}
class NetworkRequestLoaded extends NetworkState {
final dynamic response;
NetworkRequestLoaded({this.response});
#override
List<Object> get props => [this.response];
}
class NetworkRequestFailure extends NetworkState {
final String message;
NetworkRequestFailure({this.message});
#override
List<Object> get props => [this.message];
}
You can easily send request in JSON and get Response in dynamic, which you can convert to appropriate object using json.decode().