Changing the sembast database instance in GetIt after authentication - flutter

I have an AuthRepository with a currentAccount getter, which has an account id. This parameter may change depending on the current account. There is also a ScheduleRepository that accepts sembast Database and it is created in GetIt instance. The problem is that there is no Database before authentication, and after it should be created in .../$userId/schedule.db because you need the ability to store multiple accounts. Also, when you log out of your account and log in to another one, it must be recreated.
I found only GetIt.instance.reset() in the documentation, but this is not what is needed because it completely restarts GetIt. There is also a resetLazySingleton, but it requires creating an instance before that. Also, I do not know where to reset Database? In the AccountsRepositoryImpl.setAccount()? Now the initialization of ScheduleRepository looks like this:
Future<void> _initSchedule() async {
// BLoCs
sl.registerFactory(() => ScheduleBloc(getSchedule: sl()));
// Data sources
final currentAccount = sl<AccountsRepository>().currentAccount;
if (currentAccount != null) {
final database = await databaseFactoryIo.openDatabase(
join(_saveDir, currentAccount.userInfo.id, 'schedule.db'));
sl.registerLazySingleton<ScheduleLocalDataSource>(
() => ScheduleLocalDataSourceImpl(database: database));
}
sl.registerLazySingleton<ScheduleRemoteDataSource>(
() => ScheduleRemoteDataSourceImpl(eljurUriFormer: sl()));
// Repositories
sl.registerLazySingleton<ScheduleRepository>(() =>
ScheduleRepositoryImpl(localDataSource: sl(), remoteDataSource: sl()));
// Use cases
sl.registerLazySingleton(() => GetScheduleUseCase(repository: sl()));
}

Related

Have OrderCubit access the userId stored in AuthCubit

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.

EF Core - multi tenant application with 2 DB Context

Background:
.NET 6 application (front-end Angular SPA) Will be deployed as single application with 1 database per tenant. There is a shared database (called GlobalContext) which holds Tenant and TenantUser information.
In my Program.cs I don't have a connection string to the tenant database, as that information information is only available after a user has logged in.
builder.Services.AddDbContext<GlobalContext>(
options => options.UseSqlServer(config.GetValue<string>("ConnectionStringGlobal"))
);
No connection string specified here
builder.Services.AddDbContext<FinanceAppContext>();
In the OnConfiguring method in the FinanceappContext I obtain the connection string using a service;
var tenant = await _tenantService.GetConnectionString();
optionsBuilder.UseSqlServer(_config.GetValue<string>(tenant));
base.OnConfiguring(optionsBuilder);
The TenantService is a transient service which obtains the logged in users tenant from the GlobalContext
public async Task<string> GetConnectionString() {
try
{
var userId = _contextAccessor.HttpContext.User.FindFirst(ClaimTypes.Email).Value;
var user = await _globalContext.TenantUser
.Include(tenantuser => tenantuser.Tenant)
.FirstOrDefaultAsync(tenantuser => tenantuser.EmailAddress == userId);
return user.Tenant.Name;
}
catch (System.Exception)
{
return "";
}
}
But when I try to access any data via the FinanceAppContext I get the following error;
A relational store has been configured without specifying either the DbConnection or connection string to use
It seems like the fact I don't specify a connection string in Program.cs and but do specify one in OnConfiguring seems to be an issue?
So the issue was that my OnConfiguring method was marked as async and I was making an await call to my TenantService to obtain the connection string. I remove the async/await and the retrieval of the user from the GlobalContext now works.

Making an API accessible throughout all widgets - use Provider with already instantiated variable?

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');
}
}

What will be the best way to get data from some blocs?

I use that structure for getting data from blocs.
class CheckOutBloc extends Bloc<CheckOutEvent, CheckOutState> {
CheckOutBloc(
{#required this.orderRepository,
#required this.addressBloc,
#required this.cartBloc})
: super(null);
final MyOrderRepository orderRepository;
final AddressBloc addressBloc;
final CartBloc cartBloc;
#override
Stream<CheckOutState> mapEventToState(
CheckOutEvent event,
) async* {
if (event is CreateOrder) {
try {
yield CheckOutInProgress();
final address = (addressBloc.state as AddressLoadSuccess).getPrimary();
final items = (cartBloc.state as CartLoadSuccess).items;
final Map<String, dynamic> order = {
'items': items.map((e) => e.toJson()).toList(),
'addressUUID': address.uuid,
'description': '',
'storeUUID': items[0].uuid,
};
await orderRepository.createOrder(order);
yield CheckOutLoadSuccess();
} catch (_) {
yield CheckOutFailed(_?.toString());
}
}
}
}
However, I think, It is not better way to get data.
I think get data using stream of bloc like bloc.listiner(() => add()) or use repository provider where we send request to another bloc for getting data of other blocs. I can't decide which way is the best.
How you think which way is better or my option is good way.
Hard to tell from the info given. If you see all the data needed from addressBloc and from CartLoadSuccess in your ui, I would maybe send the data via the event.
It also depends on the complexity of your app and if complexity increase and data store changes in the future are likely. If not, you may also think of a repository shared by different blocs. On the other hand, if costs from requests and network traffic play a role, I guess I would use the event or your approach shown (though you may have to catch situations where the other bloc has an unexpected state (still in progress or with error)

How do I initialize a long-lived class in a Flutter app?

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.