I am making an app using the BLoC architecture using the flutter_bloc package, but I need to get data from a database, which is asynchronous, meaning I can't initialize the BLoC with data from my database. Is there a way I can do this? My BLoC class text is
import 'package:countdown/database/utils.dart';
import 'package:countdown/events/countdown_event.dart';
import 'package:countdown/models/countdown.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class HomeBloc extends Bloc<CountdownEvent, List<Countdown>> {
#override
// TODO: implement initialState
List<Countdown> get initialState => DatabaseUtils.getCountdowns();
#override
Stream<List<Countdown>> mapEventToState(CountdownEvent event) {
}
}
I know this is very similar to This Question, but that question's answers don't have any code snippets which would be very helpful.
create one separate async function and call if from initState(). this is my code you can call your method according to use.
#override
HomeState get initialState {
_checkLocationSettings();
return InitialHomeState();
}
_checkLocationSettings() async {
locationUpdateSetting = await repository.isLocationUpdateOn();}
Related
I'm trying to implement a clean architecture with no dependency of the framework in the business' logic layers.
The following example is a Screen with only a Text. I make an API Rest call in the repository and add the response to a BehaviorSubject that is listened through a StreamBuilder that will update the Text. Since is an StatefulWidget I'm using the dispose method to close the BehaviorSubject's StreamController.
The example is simplified, no error/loading state handling, no dependency injection, base classes, dispose interfaces etc.
class Bloc {
final UserReposiotry _userReposiotry;
final BehaviorSubject<int> _activeUsersCount = BehaviorSubject.seeded(0);
Bloc(this._userReposiotry) {
_getActiveUsersCount();
}
void _getActiveUsersCount() async {
final response = await _userReposiotry.getActiveUsersCount();
_activeUsersCount.add(response.data);
}
ValueStream<int> get activeUsersCount => _activeUsersCount.stream;
void dispose() async {
await _activeUsersCount.drain(0);
_activeUsersCount.close();
}
}
class StatefulScreen extends StatefulWidget {
final Bloc bloc;
const StatefulScreen({Key? key, required this.bloc}) : super(key: key);
#override
State<StatefulScreen> createState() => _StatefulScreenState();
}
class _StatefulScreenState extends State<StatefulScreen> {
#override
Widget build(BuildContext context) {
final stream = widget.bloc.activeUsersCount;
return StreamBuilder<int>(
stream: stream,
initialData: stream.value,
builder: (context, snapshot) {
return Text(snapshot.data.toString());
}
);
}
#override
void dispose() {
widget.bloc.dispose();
super.dispose();
}
}
I have the following doubts regarding this approach.
StreamBuilder cancels the stream subscription automatically, but it doesn't close the StreamController. I know that you should close it if you are reading a file, but in this case, if I don't manually close it, once the StatefulScreen is no longer in the navigation stack, could it be destroyed, or it would be a memory leak?
I've seen a lot of people using StatelessWidget instead of StatefulWidget using Stream and StreamBuilder approach, if it is really needed to close the BehaviorSubject it is a problem since we don't have the dispose method, I found about the WillPopScope but it won't fire in all navigation cases and also and more important would it be more performant an approach like WillPopScope, or having an StatefulWidget wrapper (BlocProvider) inside an StatelessWidget just to do the dispose, than using an StatefulWidget directly, and if so could you point to an example of that implementation?
I'm currently choosing StatefulWidget for widgets that have animations o controllers (map, text input, pageview...) or streams that I need to close, the rest StatelessWidget, is this correct or am I missing something?
About the drain method, I'm using it because I've encountered an error navigating back while an API rest call was on progress, I found a member of the RxDart team saying it isn't really necessary to call drain so I'm confused about this too..., the error:
You cannot close the subject while items are being added from addStream
Thanks for your time.
I have this function in a widget (homescreen):
void toggleRecording() async {
// HERE IS THE CONFUSION I GUESS
_isRecording = !_isRecording;
recorder = SoundStream(isRecording: _isRecording);
//recorder.toggleRecording(_isRecording);
setState(() {
_isRecording = recorder.isRecording;
});
if (_isRecording) {
startTimer();
_stopwatch.start();
} else {
stopTimer();
_stopwatch.stop();
}
}
It needs to call (trigger) another function in my recorder class:
void toggleRecording() async {
widget.isRecording ////// currently being passed as an argument from homescreen
? {_recorder.stop, await save(_micChunks, 44100)}
: _recorder.start;
}
Also, the boolean variable _isRecording is present in both the classes. How do I sync the state?
In your situation passing reference of function through widgets will work. However best practice of this will be using provider package.
Managing functions from provider will allow you to control functions from all pages.
If you change a variable you can call notifylistener() function inside your provider function. So that you can change state of widget.
I will try to explain it in a glance however this is an important subject of flutter.
Here is my folder structure
At provider folder we define our provider classes.
Firstly i define class which extends changeNotifier class. This is what make this class provider.
Side note: notifyListener() function here calls setState of every widget if you use any variables inside that class and this is what you are searching for.
Then i import it into my main.dart file or whatever file you want. Only condition is being above the widget that you will use provider at.
At last you can use your function at everywhere if you import provider package and define your provider like i did in this code.
At last here is the visualized stucture of provider package.
I wish i explained it well. There is more about it on youtube.
Pass the function to other widget
using Function keyword
Say ContentWidget is your child and ParentWidget is parent
class ParentWidget extends StatefulWidget {
//Do Something
void onSomeFunction()
{
ContentWidget(onTimerUpdate:onTimerClosed)
}
void onTimerClosed()
{
//Perform Operation on Timer Change
}
}
class ContentWidget extends StatefulWidget {
final Function onTimerUpdate;
ContentWidget({
Key key,
#required this.onTimerUpdate,
}) : super(key: key);
void onAnyActionFromChild()
{
widget.onTimerUpdate() //Note () can have any param or can be null
}
all the examples, I have seen, initialize ObjectBox in a State(less/full)Widget. I am using a layered architecture (currently refactoring to DDD) and wonder, how to inject my ObjectBox properly.
In my repository, I inject the data sources using the injectable and the getit packages with
#injectable
#LazySingleton (as: IJournalsRepository)
class JournalsRepository implements IJournalsRepository {
final JournalsRemoteDataSource journalsRemoteDataSource;
final JournalsLocalDataSource journalsLocalDataSource;
JournalsRepository(this.journalsLocalDataSource, this.journalsRemoteDataSource);
Those packages then create an instance of JournalsRemoteDataSource and of JournalsRemoteDataSource and inject it into the repository.
The ObjectBox example shows for initialization
class _MyHomePageState extends State<MyHomePage> {
Store? _store;
#override
void initState() {
super.initState();
openStore().then((Store store) => _store = store;);
}
#override
void dispose() {
_store?.close(); // don't forget to close the store
super.dispose();
}
}
So I am lacking an idea on how an injector could initialize ObjectBox or how I could access the objectBox object from within the injected JournalsRemoteDataSource if I would initialize objectBox in MyApp() (which is upstream to the HomePage)
PS: reopening the box in JournalsRemoteDataSource on every read/write event has a very poor performance
========== UPDATE ==========
supplementing my comment to #vaind
I have found your answer on this similar question in the meantime (not sure why I did not see it, initially). I hope to get this approach working here, too. However, I have still issues initializing the store. My prototype comes from Firestore and looks like this:
import 'package:firebase_auth/firebase_auth.dart';
import 'package:injectable/injectable.dart';
#module
abstract class FirebaseInjectableModule {
#lazySingleton
FirebaseAuth get firebaseAuth => FirebaseAuth.instance;
}
though I do not understand where the getter firebaseAuth comes from and haven't found any explanation, yet. Anyways, I adapted that to
import 'package:injectable/injectable.dart';
import 'package:objectbox/objectbox.dart';
import 'package:test/objectbox.g.dart';
#module
abstract class ObjectboxInjectableModule {
#lazySingleton
Future<Store> get store async => await openStore();
}
and use this with
#LazySingleton (as: ILocalDataSource)
class ObjectBoxDataSource implements ILocalDataSource {
final Store _store;
final Box<JournalOboxEntity> _box;
ObjectBoxDataSource(this._store) : _box = _store.box();
Besides final Store _store being grey in IntelliJ (unused variable), I receive the error
You tried to access an instance of Store that is not ready yet
'package:get_it/get_it_impl.dart':
Failed assertion: line 404 pos 9: 'instanceFactory.isReady'
So following another answer of vaind, I implemented this as follows. My architecture follows a merge of Reso Coder's DDD and Clean Architecture tutorials. Basically it is DDD with the local/remote data source layer of Clean Architecture.
INFRASTRUCTURE directory
abstract data sources
abstract class ILocalDataSource {
Future<JournalDto> getJournal(int id);
Future<void> storeJournal(JournalDto record);
}
abstract class IRemoteDataSource {
Future<JournalDto> getJournal(int problemClassId);
}
data source implementation
#LazySingleton (as: ILocalDataSource)
class ObjectBoxDataSource implements ILocalDataSource {
final Store _store;
final Box<JournalOboxEntity> _box;
ObjectBoxDataSource(this._store) : _box = _store.box();
injectable module in infrastructure/core
#module
abstract class ObjectBoxInjectableModule {
#preResolve // <<<<<<<<<<<<< needed for async init
#lazySingleton
Future<Store> get store async => await openStore();
}
And now the trick to get it work: My later errors where caused by an injector init not yet finished. After changing injection.dart in the root folder to a Future and awaiting the call in main(), it worked. injection.dart now looks like this:
final GetIt getIt = GetIt.instance;
#injectableInit
Future<void> configureInjection(String env) async {
$initGetIt(getIt, environment: env);
}
I don't have experience with packages get_it & injectable, but from the docs, I think the following alternatives would work. Using get_it directly, not sure about the right way to achieve the same with injectable (generator for get_it) but I guess if you're familiar with it you can configure it to generate the same code.
Alternative A, lazy (async) singleton
GetIt.I.registerSingletonAsync<Store>(openStore);
Alternative B, setup in main(), probably preferrable
change your main to sth like:
void main() async {
GetIt.I.registerSingleton<Store>(await openStore());
runApp(MyApp());
}
Note: Looks like get_it provides a way to reset, which would result in reopening the same store. To avoid issues if you use that, you'd also need to implement a version of get_it's dispose that calls store.close().
I am new to Flutter and I have this simple use case: in my Cloud Firestore DB I have a list of JSON representing events. I want to show them through my Flutter app in a ListView.
My requirements is that the ListView doesn't refresh in real-time but only when a pull-on refresh (implemented using RefreshIndicator) is done by the user or when the app resumes from background
I tried to implement this in 2 ways (I am using provider package for state management):
Using StreamProvider to create a stream of records from the DB. This continuosly updates the list view (basically the widget changes while the user is looking at it and I don't want this)
Using a ChangeNotifierProvider that refers to a EventManager class which holds a List<Event>. This class has a pull method which updates its internal state. I call this method when the user does the pull-on refresh (in the onRefresh callback of RefreshIndicator).
Option 2 seems to work well however I do not know how to implement the refresh when the app resumes from background. As I said I am using provider (and therefore StatelessWidget) and apparently there is no way to bind to these events when using StatelessWidgets
Do you have any suggestions and best practices for this use case?
You need to access Flutters lifecycle methods and fire a callback when the app resumes.
You can add a stateful widget with WidgetsBindingObserver and put that somewhere in the scope of your Provider, but as a parent of whatever widget you use to display the info.
Or you can make your PullToRefresh widget stateful and do the same thing.
class LifeCycleWidget extends StatefulWidget {
#override
_LifeCycleWidgetState createState() => _LifeCycleWidgetState();
}
class _LifeCycleWidgetState extends State<LifeCycleWidget>
with WidgetsBindingObserver {
AppLifecycleState _appLifecycleState;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
refreshOnResume();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
setState(() {
_appLifecycleState = state;
});
refreshOnResume();
}
void refreshOnResume() {
if (_appLifecycleState == AppLifecycleState.resumed) {
print('resumed');
// your refresh method here
}
}
#override
Widget build(BuildContext context) {
return HomePage();
}
}
Add the following to your main method if it's not there already.
WidgetsFlutterBinding.ensureInitialized();
Another way to do it without adding a stateful widget would be with GetX. You can still keep all your Provider stuff but only use the SuperController which provides lifecycle methods. This I can't test because I don't have your Provider code but you can probably get away with creating the class below and initializing the controller somewhere within the scope of the relevant Provider widget with
Get.put(LifeCycleController());
Then call the function in the onResumed override and you can use Get.context if you need context.
class LifeCycleController extends SuperController {
#override
void onDetached() {
debugPrint('on detached');
}
#override
void onInactive() {
debugPrint('on inactive');
}
#override
void onPaused() {
debugPrint('on pause');
}
#override
void onResumed() {
// your refresh function here. Access context with Get.context
debugPrint('on resume');
}
}
For managing pull-to-refresh indication in Flutter with BLoC I created a custom Completer hook as an alternative to using a Stateful Widget, and in general, it works fine, however with hot-reload I run into Bad state: Future already completed
import 'dart:async';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
class _CompleterHook extends Hook<Completer> {
#override
HookState<Completer, Hook<Completer>> createState() => _CompleterHookState();
}
class _CompleterHookState extends HookState<Completer, _CompleterHook> {
Completer _completer;
#override
void initHook() {
_completer = Completer<void>();
super.initHook();
}
#override
Completer build(BuildContext context) => _completer;
}
Completer<void> useCompleterHook() {
return Hook.use(_CompleterHook());
}
Not sure if there is perhaps something I missed, it's literally only on the HR so completely a development annoyance, I would prefer not to learn to live with it though. Any ideas or suggestions on ways to fix that.
TIA