Arguments of a constant creation must be constant expressions on Flutter - flutter

I used Flavor and define two main classes. And also I used Chopper. How to set baseUrl based one environment?
import 'package:meta/meta.dart';
enum BuildFlavor { production, development }
BuildEnvironment get env => _env;
BuildEnvironment _env;
class BuildEnvironment {
final String ssoBaseUrl;
final BuildFlavor flavor;
BuildEnvironment._init(
{this.ssoBaseUrl,
this.flavor});
static void init(
{#required flavor,
#required ssoBaseUrl}) =>
_env ??= BuildEnvironment._init(
flavor: flavor,
ssoBaseUrl: ssoBaseUrl);
}
main.dev.dart
void main() {
BuildEnvironment.init(
flavor: BuildFlavor.development,
ssoBaseUrl: 'http://15.88.219.20');
assert(env != null);
runApp(MultiProvider(
providers: globalProviders,
child: MyApp(),
));
}
Chopper
#ChopperApi(baseUrl:'${env.ssoBaseUrl}')
abstract class Services extends ChopperService {
static Services create([ChopperClient client]) => _$Services(client);
#Post(path: '/sso-dev/registration')
Future<Response> registerPost(
Error Message

Annotations have to be provided with const values only. Since env is a getter property, it is not const. I'm afraid you won't be able to specify a baseUrl this way.

Related

How to configure Flutter Flavor for different API links

I have 3 different API links for Staging, Development and Production Stages. I tried to configure the files in this way to share links
//Types of flavors we have
import 'dart:io';
//Types of flavors
enum Flavor {
STAGING,
DEVELOPMENT,
PRODUCTION,
}
class Config {
//Floavor is the way to devide application
//configurations depending on stage we work
static Flavor? appFlavor;
// api url for requests depending on the flavor
// you can use it by typing Config.api_url
static String get api_url {
switch (appFlavor) {
case Flavor.PRODUCTION:
return 'https://api.Link1';
case Flavor.DEVELOPMENT:
return 'https://api.Link2';
case Flavor.STAGING:
return 'https://api.Link3';
default:
return 'https://api.Link2';
}
}
//getting information about platform
//you can use it by typing Config.platform
static String get platform => Platform.isAndroid ? 'ANDROID' : 'IOS';
}
Then I initialise the flavor in main.dart in the following way :
void main() {
//Initialising the flavor
Config.appFlavor = Flavor.PRODUCTION;
Bloc.observer = AppBlocObserver();
FlutterError.onError = (details) {
log(details.exceptionAsString(), stackTrace: details.stack);
};
runZonedGuarded(
() => runApp(const IChazy()),
(error, stackTrace) => log(error.toString(), stackTrace: stackTrace),
);
}
But then when I try to pass the data to Chopper package for baseURL it shows me mistake that I should use the const value:
import 'package:chopper/chopper.dart';
import 'package:ichazy/config/flavor_config.dart';
//part 'challenges_api_service.chopper.dart';
#ChopperApi(baseUrl: Config.api_url) //Error Appears here
abstract class ChallengeApiService {}
The Error:
Const variables must be initialized with a constant value.
Try changing the initializer to be a constant expression.
I know that I should use const value in order to cancel this error but in the same time I want to switch flavors and API links. Is there any proper way to somehow manage this two goals, maybe by changing Config class?
Thank you in advance.
The only way I found is to use custom create method in the chopper package
#ChopperApi(baseUrl: '/')
abstract class ChallengeApiService extends ChopperService {
#Get()
Future<Response> getChallenges(
#Body() Map<String, dynamic> body,
);
static ChallengeApiService create() {
final client = ChopperClient(
baseUrl: Config.api_url,
services: [_$ChallengeApiService()],
converter: JsonConverter());
return _$ChallengeApiService(client);
}
}

Should I use multiple classes for bloc states or one class with multiple constructors?

I have started learning bloc recently and I noticed some tutorials use multiple classes for states and others just use one class with multiple named constructors.
Example for multiple classes:
abstract class ProductsState {}
class ProductsInitial extends ProductsState {}
class ProductsLoading extends ProductsState {}
class ProductsSuccess extends ProductsState {
final List<Products> products;
ProductsSuccess(this.products);
}
class ProductsError extends ProductsState {
final String error;
ProductsError(this.error);
}
Example for multiple constructors:
enum AuthenticationStates {
initial,
success,
error,
}
class AuthenticationState {
final AuthenticationStates state;
final Authentication model;
final String msg;
const AuthenticationState._(
{this.state = AuthenticationStates.initial,
this.model,
this.msg = ''});
const AuthenticationState.initial() : this._();
const AuthenticationState.success(Authentication mdl)
: this._(model: mdl, state: AuthenticationStates.success);
const AuthenticationState.error(String m)
: this._(state: AuthenticationStates.error, msg: m);
}
Which one is better to use?
In my projects, I use the first way since different state types are clear, you can later build your UI based on that e.g. if (state is ProductsLoading) { show loader }.
You can also generate such classes by using the freezed package (https://pub.dev/packages/freezed). When using it, you would define a single class with different factory constructors (similar to your second option), but the generated code would support the first option you've defined, e.g.:
#freezed
class Union with _$Union {
const factory Union(int value) = Data;
const factory Union.loading() = Loading;
const factory Union.error([String message]) = ErrorDetails;
}
Later, in the UI layer, you could use helper methods like map/maybeMap/when/maybeWhen to build the corresponding view based on state, e.g.:
var union = Union(42);
print(
union.when(
(int value) => 'Data $data',
loading: () => 'loading',
error: (String? message) => 'Error: $message',
),
); // Data 42
I was asking myself the same question.
"The moment you check the concrete type of an instance in your logic, know you're not using polymorphism correctly." - Back in the university.
In the documentation of BloC they use one class with enum in all examples(or extra status class),
the enum will be composed of something similar to SUCCESS FAILURE etc.
Check the PostState mentioned in BloC documentation as example:
https://bloclibrary.dev/#/flutterinfinitelisttutorial?id=post-states
Nevertheless, the multiple sub-classes for state is a commonly used practice among flutter devs. It looks wrong for me, but who knows...

what is the correct approach to test riverpod with mockito

what is the correct approach to test riverpod with mockito?
running the code above,
/// ### edited snippets from production side ###
/// not important, skip to the TEST below!
/// this seems meaningless just because it is out of context
mixin FutureDelegate<T> {
Future<T> call();
}
/// delegate implementation
import '../../shared/delegate/future_delegate.dart';
const k_STRING_DELEGATE = StringDelegate();
class StringDelegate implements FutureDelegate<String> {
const StringDelegate();
#override
Future<String> call() async {
/// ... returns a string at some point, not important now
}
}
/// the future provider
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '<somewhere>/delegate.dart'; /// the code above
final stringProvider = FutureProvider<String>((ref) => k_STRING_DELEGATE());
/// ### edited snippets from TEST side ###
/// mocking the delegate
import 'package:mockito/mockito.dart';
import '<see above>/future_delegate.dart';
class MockDelegate extends Mock implements FutureDelegate<String> {}
/// actual test
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/all.dart';
import 'package:mockito/mockito.dart';
import '<somewhere in my project>/provider.dart';
import '../../domain/<somewhere>/mock_delegate.dart'; // <= the code above
void main() {
group('`stringProvider`', () {
final _delegate = MockDelegate();
test('WHEN `delegate` throws THEN `provider`return exception',
() async {
when(_delegate.call()).thenAnswer((_) async {
await Future.delayed(const Duration(seconds: 1));
throw 'ops';
});
final container = ProviderContainer(
overrides: [
stringProvider
.overrideWithProvider(FutureProvider((ref) => _delegate()))
],
);
expect(
container.read(stringProvider),
const AsyncValue<String>.loading(),
);
await Future<void>.value();
expect(container.read(stringProvider).data.value, [isA<Exception>()]);
});
});
}
running the test returns
NoSuchMethodError: The getter 'value' was called on null.
Receiver: null
Tried calling: value
dart:core Object.noSuchMethod
src/logic/path/provider_test.dart 28:48 main.<fn>.<fn>
I'm new to riverpod, clearly I'm missing something
I tried to follow this
I found that I had some extra errors specifically when using StateNotifierProvider. The trick was to not only override the StateNotifierProvider, but also its state property (which is a StateNotifierStateProvider object).
class SomeState {
final bool didTheThing;
SomeState({this.didTheThing = false});
}
class SomeStateNotifier extends StateNotifier<SomeState> {
SomeStateNotifier() : super(SomeState());
bool doSomething() {
state = SomeState(didTheThing: true);
return true;
}
}
final someStateProvider = StateNotifierProvider<SomeStateNotifier>((ref) {
return SomeStateNotifier();
});
class MockStateNotifier extends Mock implements SomeStateNotifier {}
void main() {
final mockStateNotifier = MockStateNotifier();
when(mockStateNotifier.doSomething()).thenReturn(true);
final dummyState = SomeState(didTheThing: true); // This could also be mocked
ProviderScope(
overrides: [
someStateProvider.overrideWithValue(mockStateProvider), // This covers usages like "useProvider(someStateProvider)"
someStateProvider.state.overrideWithValue(dummyState), // This covers usages like "useProvider(someStateProvider.state)"
],
child: MaterialApp(...),
);
}
There are 2 errors in your code
You're trying to test a throw error, so you should use thenThrow instead of thenAnswer, but because you're overriding a mixing method I would recommend instead of using Mock use Fake (from the same mockito library) to override methods and then throw it as you want
class MockDelegate extends Fake implements FutureDelegate<String> {
#override
Future<String> call() async {
throw NullThrownError; //now you can throw whatever you want
}
}
And the second problem (and the one your code is warning you) is that you deliberately are throwing, so you should expect an AsyncError instead, so calling container.read(stringProvider).data.value is an error because reading the riverpod documentation:
When calling data:
The current data, or null if in loading/error.
so if you're expecting an error (AsyncError) data is null, and because of that calling data.value its the same as writing null.value which is the error you're experiencing
This is the code you could try:
class MockDelegate extends Fake implements FutureDelegate<String> {
#override
Future<String> call() async {
throw NullThrownError;
}
}
void main() {
group('`stringProvider`', () {
final _delegate = MockDelegate();
test('WHEN `delegate` throws THEN `provider`return exception', () async {
final container = ProviderContainer(
overrides: [
stringProvider
.overrideWithProvider(FutureProvider((ref) => _delegate.call()))
],
);
expect(container.read(stringProvider), const AsyncValue<String>.loading());
container.read(stringProvider).data.value;
await Future<void>.value();
expect(container.read(stringProvider), isA<AsyncError>()); // you're expecting to be of type AsyncError because you're throwing
});
});
}
Also consider mocking out various providers by using an Override in your top level ProviderScope. That's what override can do quite well.

Dart dependency injection of a class with parameters

I am trying to learn about dependency injection and trying to use it in Flutter/Dart. I have gone through the library get_it which I find very useful.
But I am having a doubt about the dependency injection in a specific situation.
If I have a Dart class with parameters like this one for example:
class MyClass(){
final String name;
MyClass({this.name})
....
....
}
In such a class, with parameters, It seems like I cannot use dependency injection? or at least the following using get_it will not work:
**** service_locator.dart ****
import 'package:get_it/get_it.dart';
GetIt locator = GetIt();
void setupLocator() {
locator.registerLazySingleton<MyClass>(() => MyClass());
}
This gives error on => MyClass()....since it is expecting a parameter.
How to do this kind of injection?.
You just pass the argument you want to MyClass(). You don’t have to do it inside setUpLocator(). Register the singleton, anywhere in your program, when you know what argument to pass.
For example, if you need to register a user object as a singleton, you’ll have to do it after the user logs in and all their info is available in order to properly instantiate your User class.
because it is a singleton you only need to call myClass.name = 'name' once, anywhere in your app and it will change everywhere.
example app:
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
super.initState();
GetIt.instance.registerLazySingleton<MyClass>(()=> MyClass());
// Name from a future
get('https://jsonplaceholder.typicode.com/users').then((response){
if(response.statusCode==200) {
setState(() {
GetIt.I.get<MyClass>().name = jsonDecode(response.body)[0]['name'];
});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Title'),
),
body: Container(
child: Center(
child: Text(GetIt.I.get<MyClass>().name),
),
),
);
}
}
class MyClass{
String name = '';
}
another way is that because it's built using a lazy builder, the MyClass object will only be built after the first locator.get<MyClass>() is called so you can prepare and get it from a static variable.
There is no issue passing a string into the constructor of your class. You only need access to the value you want to pass into the MyClass constructor. One way to do that is to get the value from another dependency. In this example, you can grab it from AppOptions.
import 'package:ioc_container/ioc_container.dart';
class MyClass {
final String name;
MyClass({required this.name});
}
class AppOptions {
String get myClassName => 'Bob';
}
void main(List<String> arguments) {
final builder = IocContainerBuilder()
..addSingletonService<AppOptions>(AppOptions())
..addSingleton(
(container) => MyClass(name: container<AppOptions>().myClassName));
final container = builder.toContainer();
final myClass = container<MyClass>();
print(myClass.name);
}
Grab the package here
This prints
Bob

Using context outside stateless or stateful widgets

Im going to explain my use case for this
I have a inherited widget that has some values like BaseApiUrl and appname
class Config extends InheritedWidget {
Config({
#required this.appName,
#required this.flavorName,
#required this.apiBaseUrl,
#required Widget child,
}) : super(child: child);
final String appName;
final String flavorName;
final String apiBaseUrl;
static Config of(BuildContext context) {
return context.inheritFromWidgetOfExactType(Config);
}
#override
bool updateShouldNotify(InheritedWidget oldWidget) => false;
}
This works fine inside of context based classes, but what if i have this class where i need apiBaseUrl
import 'dart:async';
import 'package:http/http.dart';
class LoginRequest {
Client client = Client();
Future<Response> login(credentials) async => await client.get('');
}
Can be this accomplished ?
The context is important so Flutter knows where in the tree to look for the instance of the InheritedWidget, since there could be multiple. If this isn't true for your use case, you could consider moving the required data into a plain singleton class (not a widget).