Where do I put commands that run once at beginning? - flutter

I really miss C programming. Flutter is quite confusing.
Here is the problem:
We have a function within the Stateful class Home. That creates a page called myMenu.
class Home extends StatefulWidget {
myMenu createState() => myMenu();
}
class myMenu extends State<Home>
{
void myProblemFunction(String stringItem) async {
final db = await myDatabaseClass.instance.database;
...
}
Whenever myProblemFunction runs, it will instantiate a new database instance.
I just want to put this command once (i.e.:
final db = await myDatabaseClass.instance.database
) anywhere in my document (this is my main.dart file (by the way). Now, I could put it in the initState() - sure.. but that's a headache, because a) I would have to make it async, and I feel that will cause me problems, and b) declaring final db... (etc) in the initState() will not make the db variable visible to my functions.
What do I do?
what are the elements in myMenu Class that are triggered? Should I put this function inside the widget build method? But surely if the widget is refreshed, then this function will also be called (again) - I just want to call it once.
Thanks

you can use FutureBuilder
example:
FutureBuilder(
future: myDatabaseClass.instance.database,
builder: (BuildContext context,AsycnSnapshot snapshot){
//your code here
},
)
this will only load the Future once when the widget is built.

put your code that you want to run only once in initState():
#override
void initState() {
super.initState();
// TODO code you want to run only once
}

Related

flutter : run provider function to load data before/while widget is loading the layout

Structure of code:
I have a function, that on a Button click returns a stateful widget A.
In a separate file, I have a ChangeNotifier class B, which A
needs (it needs the decoded json file) and I use the ChangeNotifier class as a general memory container which all the different widgets have access to.
B:
class Database_Controller extends ChangeNotifier {
Future<void> _readVeryLargeJsonFrame() async {
final String response =
await rootBundle.loadString('assets/tables/largejson.json');
final data = await json.decode(response);
return data;
}
Other functions to give back entries of data
}
Problem:
I would like to execute _readVeryLargeJsonFrame as soon as A is called (potentially with a loading spinner for the user) and before A is loaded (or at least in parallel).
How do I call the ChangeNotifier function in the "init" part of a stateful widget? Peter Koltai mentioned the initState method. But how do I call the ChangeNotifier function from this?
(2. Context problem: So far, I would be using Provider.of<Database_Controller>(context,listen: false)._readVeryLargeJsonFrame(); but how do I get the context argument here?)
(3. Is the Future<void> ... async nature of the _readVeryLargeJsonFrame function a problem here?)
void initState() {
super.initState();
WidgetsBinding.instance
.addPostFrameCallback((_) => yourFunction(context));
}

Flutter clean architecture with Bloc, RxDart and StreamBuilder, Stateless vs Stateful and dispose

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.

How do you initialise a provider with data taken from shared preferences?

I am new to flutter and dart so the answer to this may be simple, or I may be going about this COMPLETELY the wrong way.
I basically want to use Provider for state management, and Shared Preferences for local storage.
I have a class called 'ResolutionsProvider' which contains a list of custom 'Resolution' objects
which are used throughout my app. It contains methods for adding and removing Resolutions and it all works fine.
The problem is that I want the data to be persistent if the user restarts the app...
I'm trying to use SharedPreferences for this so I have methods within 'ResolutionsProvider' to save data to, and load data from Shared Preferences...
I know through print calls that the data is saving to shared preferences correctly...
My (potentially wrong) thinking is that I need to achieve the following...
App is run
ResolutionsProvider is instantiated as a ChangeNotifierProvider
ResolutionsProvider.loadData() is run to populate List from SharedPreferences...
I've tried to call ResolutionsProvider.loadData() from within an initState but I get the following error...
E/flutter (30660): [ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: dependOnInheritedWidgetOfExactType<_InheritedProviderScope<ResolutionsProvider?>>() or dependOnInheritedElement() was called before _AppState.initState() completed.
E/flutter (30660): When an inherited widget changes, for example if the value of Theme.of() changes, its dependent widgets are rebuilt. If the dependent widget's reference to the inherited widget is in a constructor or an initState() method, then the rebuilt dependent widget will not reflect the changes in the inherited widget.
E/flutter (30660): Typically references to inherited widgets should occur in widget build() methods. Alternatively, initialization based on inherited widgets can be placed in the didChangeDependencies method, which is called after initState and whenever the dependencies change thereafter.
So i guess the question is two fold...
If my approach is correct, how do I call the 'loadData' method to populate my provider on booting the app if I can't do so within initState
If my approach is incorrect, what's the right approach to achieve my basic objective?
Here's my main.dart code
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await LocalStorage.init();
runApp(
ChangeNotifierProvider(
create: (context) => ResolutionsProvider(),
child: const App(),
),
);
}
Here's the relevant code from my App.dart
class App extends StatefulWidget {
const App({Key? key}) : super(key: key);
#override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
#override
void initState() {
loadData();
print("app initialised");
super.initState();
}
void loadData() async {
await Provider.of<ResolutionsProvider>(context).loadResolutions();
}
#override
Widget build(BuildContext context) {
return MaterialApp(blah blah blah);
Here's the relevant parts of my ResolutionProvider.dart
class ResolutionsProvider extends ChangeNotifier {
List<Resolution> resolutions = [];
// Interfacing with Shared Preferences
loadResolutions() async {
resolutions = await LocalStorage.getResolutions();
print('${resolutions.length} resolutions loaded');
// notifyListeners();
}
saveResolutions() async {
LocalStorage.saveResolutions(resolutions);
}

Flutter - How to call functions (and variables) between stateful widgets?

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
}

How to create a dependency for ChangeNotifierProvider and make it wait to complete?

I have ChangeNotifierProvider object that uses data stored sqflite asset database which need to be loaded at the beginning as future. The problem is that ChangeNotifierProvider doesn't wait for future operation to complete. I tried to add a mechanism to make ChangeNotifierProvider wait but couldn't succeed. (tried FutureBuilder, FutureProvider, using all together etc...)
Note : FutureProvider solves waiting problem but it doesn't listen the object as ChangeNotifierProvider does. When I use them in multiprovider I had two different object instances...
All solutions that I found in StackOverflow or other sites don't give a general solution or approach for this particular problem. (or I couldn't find) I believe there must be a very basic solution/approach and decided to ask for your help. How can I implement a future to this code or how can I make ChangeNotifierProvider wait for future?
Here is my summary code;
class DataSource with ChangeNotifier {
int _myId;
List _myList;
int get myId => _myId;
List get myList => _myList;
void setMyId(int changeMyId) {
_myId = changeMyId;
notifyListeners();
}
.... same setter code for myList object.
DataSource(){initDatabase();}
Future<bool> initDatabase() {
.... fetching data from asset database. (this code works properly)
return true;
}
}
main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<DataSource>(
create: (context) => DataSource(),
child: MaterialApp(
home: HomePage(),
),
);
}
}
Following code and widgets has this code part (it works fine)
return Consumer<DataSource>(
builder: (context, myDataSource, child) {.......
There are multiple ways that you can achieve. The main point of it is that you should stick to reactive principle rather than trying to await the change. Say for example, you could change the state of boolean value inside the DataSource class when the ajax request changes
class DataSource extends ChangeNotifier{
bool isDone = false;
Future<bool> initDatabase(){
//Do Whatever
isDone = true;
notifyListeners();
}
}
Then you could listen to this change in the build method like so
Widget build(BuildContext ctx){
bool isDone = Provider.of<DataSource>(context).isDone;
if(isDone){
// render result
}else{
// maybe render loading
}
}