I have an Api class that accepts an (optional) authentication token that it uses for making authenticated requests e.g. Api(token: 'a9sa2ksas12').getUserDetails().
If the token is not passed, it has to perform the relatively expensive operation of reading it from sharedPreferences.
class Api {
static const BASEURL = "https://api.google.com/";
final String token;
Api({ this.token });
Future<http.response> getUserDetails() async {
return http.get('$BASEURL/user/', headers: { 'Authorization': token });
}
}
How can I setup my app so that the token is read only once from sharedPreferences and used throughout the app for all future Api() requests?
Some ideas I've considered and think that may work:
Have token be a global variable
Make the API class a singleton, and pass it around between "screens"
Well in general there's nothing bad in making you Repositories a singletons. But on the other hand, I don't like the concept of passing the API classs between the screens.
When widgets use your data source directly without any middleman, like Bloc or Provider, they tend to be polluted with a lot of presentation logic. I personally prefer to separate those concerns and let widgets do what they are made for: rendering the UI. This makes them smaller and easier to test.
What's more the API class should be responsible only for the network calls. It shouldn't be responsible for managing the token. I'd inject to the API class something like:
class TokenProvider
{
Future<String> getToken();
}
The responsibility of this class would be, you guessed it, to provide a token. It can return cached value or get it from the SharedPreferences. Thanks to this the API doesn't have to care where the token comes and how to handle it. It will do just one thing: api calls.
I ended up using the "Locator" pattern, via get_it.
The code was pretty simple.
Step 1: Setup a top-level locator.dart in lib/
// ./lib/locator.dart
import 'package:my_app/services/api.dart';
import 'package:get_it/get_it.dart';
GetIt locator = GetIt.instance;
void setupLocator() {
locator.registerLazySingleton(() => Api());
}
Step 2: Use api anywhere in the app by just importing the locator:
// ./lib/widgets/some_screen.dart
class _SomeScreenState extends State<SomeScreen> {
Api api = locator<Api>();
#override
void initState() {
api.getUserDetails().then((response) => {
// do anything you like with the response
});
super.initState();
}
The beauty of this approach is that Api is only ever initialized ONCE in the lifetime of the app, so I simply assign a token to it on initState without worrying about the widget getting disposed/rebuilt and repeated fetches to SharedPreferences.
Related
I am trying to create a fetchUserOrders() method in an OrderCubit. To do so, I need the userId which is held in the OrderState of the OrderCubit.
Both Cubits are setup in a MultiBlocProvider in main.dart.
How do I make a call like:
Future<void> fetchOrders() async {
emit(OrderState.loading());
final uid = context.read<AuthCubit>().state.uid;
final orders = await OrderRepo().getUserOrders(userId: uid);
emit(OrderState.loaded(orders));
}
within the OrderCubit?
I don't know of a way to get a context within a Cubit, so the above is not an option thus far. I have looked at using BlocListener, but as I understand it that only emits a new state in response to a change from AuthCubit - which is not refreshing a new value.
I appreciate any insight on best practice for reading values from one Cubit to another.
I need to call a provider data from a class Which doesn't extend any state classes
class Services {
static listenForMessage(){
var handler = webSocketHandler((webSocket) {
webSocket.stream.listen((message) {
Provider.of<ProviderClass>(context).chatsList.clear();// context unavailable
webSocket.sink.add("echo $message");
});
});
serve(handler, 'localhost', 8080).then((server) {
print('Serving at ws://${server.address.host}:${server.port}');
});
}
}
But there is no context available in this class, how can I call this data without BuildContext
did you try to receive the context in the function arguments and send it where your function is required?
static listenForMessage(context){...}
i have found a link that might help you, not 100% sure
https://maneesha-erandi.medium.com/handling-api-requests-with-flutter-provider-944e3258c5ca
I wrote an API class that, obviously, encapsulates a couple http requests. However, this API class also stores a little bit of state: Namely, it saves an authentication token that will be used in all subsequent requests after the first.
In my flutter function, I then first wait for that token to be set before doing anything else:
Future<void> main() async {
var api = MyAPI();
await api.auth.refreshTokens(); // Sets api.auth.token
runApp(MaterialApp(etc));
What is important here is that in etc I am also setting Providers, one of which being that very API:
providers: [Provider(create: (context) => api), ...]
which I then want to use all throughout my app. However, is that really the best way to go about it? It seems like a really really cumbersome and messy approach to simply be able to use this more or less global API object.
How is it usually done?
You can write that class as a singleton, this way you can call the constructor and obtaining each time the same instance without creating a new one:
void main() {
Api().call();
Api().call();
}
class Api {
static Api? _instance;
Api._();
factory Api() {
_instance ??= Api._();
return _instance!;
}
String token = '';
void call() {
token += '*';
print('new token $token');
}
}
I tried to mock database to test my local api, i search in official document finding mockito which can work with remote api fine, but also can not work with local database out of box, is there any way to work around of it?
In these cases, you have two options (among many others). Even if my examples assume you're making HTTP calls, it doesn't matter. You can use these strategies regardless the specific use case I'm exposing!
The first one is using the "Strategy pattern" to create an interface for the API and then switch between a test and a production API. Here's a simple example:
abstract class HttpRepository {
const HttpRepository();
Future<Something> sendRequest();
}
You can now create 2 concrete classes: one is for the actual API call and the other is just a mock for tests.
/// Use this in your tests
class MockHttpRepository extends HttpRepository {
const MockHttpRepository();
#override
Future<Something> sendRequest() async {
// Simulating the HTTP call
await Future.delayed(const Duration(seconds: 2));
return Something();
}
}
/// Use this in your Flutter code to make the actual HTTP call or whatever else
class ApiHttpRepository extends HttpRepository {
const ApiHttpRepository();
#override
Future<Something> sendRequest() async {
// Doing a real HTTP call
final response = await makeGetOrPost();
return Something.withData(response);
}
}
In this way, you'll use ApiHttpRepository in your Flutter app and MockHttpRepository in tests. Use const constructors whenever possible.
The other way is using mocks to simulate fake HTTP calls or anything else. Basically, you're using when to "trap" a method call and return a fake response you can control.
// 1. "Enable" mocking on your type
class MockRepo extends Mock implements ApiHttpRepository {}
// 2. Mock methods
const ApiHttpRepository repo = MockRepo();
when(repo.sendRequest()).thenAnswer((_) async => Something());
In this case, we're using thenAnswer because the return type of sendRequest() is of type Future<T>. In your case, if you are reading data from a database you just need to:
Make your class "mockable" using extends Mock implements YourClass
Use when on the mockable instance and control the output
Make sure to use thenAnswer if the method returns a Future<T> and thenReturn in all the other cases.
I am developing an android / ios application in flutter, and I have chosen to use redux for my state management.
I am writing unit tests for my redux actions, which have been implemented using the async_redux package.
I am following the excellent guidelines set out for testing by the author of the package, but I am not sure how to mock the dispatch of further actions from my action under test.
For example, the below LogoutAction dispatchs a DeleteDatabaseAction and waits for it to complete:
class LogoutAction extends ReduxAction<AppState> {
#override
Future<AppState> reduce() async {
await dispatchFuture(DeleteDatabaseAction());
return AppState.initialState();
}
}
class DeleteDatabaseAction extends ReduxAction<AppState> {
#override
FutureOr<AppState> reduce() {
throw StateError(
'Unwanted call to runtime implementation of DeleteDatabaseAction',
);
}
}
void main() {
final store = Store<AppState>(initialState: AppState(loggedIn: true));
final storeTester = StoreTester.from(store);
test('Logout action should return correct state and not throw StateError', () async {
storeTester.dispatch(LogoutAction());
TestInfo<AppState> info = await storeTester.wait(LogoutAction);
expect(info.state.loggedIn, false);
});
}
I want to test only the action under test, and stub out all further action calls.
i.e. How can I mock / stub the dispatch and dispatchFuture methods on ReduxAction, so that the runtime DeleteDatabaseAction implementation is not run?
So far I have attempted:
Inject DeleteDatabaseAction using get_it and inject a mock during test
I have 100+ actions that will now need to be added to my context
Some actions have parameters that change based on where they are called from, so cannot be registered at app startup
Subclass Store, override the above methods and use the subclass in my test here final store = Store<AppState>(initialState: AppState(loggedIn: true))
I will not be able to dispatch my action under test, as it uses the same store in the async_redux test implementation
Here: storeTester.dispatch(LogoutAction());
Create a separate Dispatcher implementation, inject this and override with a mock during tests
This will work, but it is new framework, I can go this route but now I am deviating from the well documented framework provided by asyn_redux
This wasn't available when you asked this question. But now the answer is here: https://pub.dev/packages/async_redux#mocking-actions-and-reducers
To mock an action and its reducer, start by creating a MockStore in your tests, instead of a regular Store. The MockStore has a mocks parameter which is a map where the keys are action types, and the values are the mocks. For example:
var store = MockStore<AppState>(
initialState: initialState,
mocks: {
MyAction1 : ...
MyAction2 : ...
...
},
);
However, there are other ways:
As you said, use get_it or some other dependency injection code to inject the http client into the action reducer. This works well.
Use a DAO. For example:
class DeleteDatabaseAction extends ReduxAction<AppState> {
#override
Future<AppState> reduce() {
await dao.deleteDatabase();
return null;
}
Then you can mock the DAO itself, or inject the DAO via get_it. You can also make the dao a getter to some BaseAction (that extends ReduxAction) and inject it there.