On the code below,
profileBloc is initialized in EditProfileScreenState's didChangeDependencies() method.
Should we be calling dispose method on EditProfileScreenState class to dispose the profileBloc ?
If so , how should the profileBloc method be disposed as ProfileBloc class extends Bloc class which doesn't have dispose method?
class Profile extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => ProfileBloc(AuthRepo()),
child: ProfileScreen(),
);
}
}
class ProfileScreen extends StatefulWidget {
#override
EditProfileScreenState createState() => EditProfileScreenState();
}
class EditProfileScreenState extends State<ProfileScreen> {
ProfileBloc profileBloc;
#override
void didChangeDependencies() {
profileBloc = BlocProvider.of<ProfileBloc>(context);
super.didChangeDependencies();
}
#override
void dispose() {
// TODO: implement dispose
//profileBloc.dispose() cannot call as ProfileBloc class doesn't have dispose method
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocConsumer<ProfileBloc, ProfileState>(
listener: (context, state) {
},
builder: (BuildContext context,ProfileState state) {
return RaisedButton(onPressed: ()=>profileBloc.add(SaveProfile("name","email")));
}
));
}
}
class ProfileBloc extends Bloc<ProfileEvent, ProfileState> {
AuthRepo authRepo;
ProfileBloc(this.authRepo) : super(ProfileSaved());
#override
Stream<ProfileState> mapEventToState(ProfileEvent event) async* {
if (event is SaveProfile) {
//Actions
}
}
}
While I was searching, I found the solution.
We don't need to initialize the profileBloc in didChangeDependencies().
We can access the add method directly from the BlocProvider using:
BlocProvider.of<ProfileBloc>(context).add(ProfileSaved())
We can remove following section from EditProfileScreenState class.
ProfileBloc profileBloc;
#override
void didChangeDependencies() {
profileBloc = BlocProvider.of<ProfileBloc>(context);
super.didChangeDependencies();
}
Moreover,
In ProfileBloc class we can use close method in case we need to cancel any streams.
#override
Future<void> close() {
//cancel streams
super.close();
}
Related
I'm trying to read value passed from a Stateful object to another Stateful object. I've defined a simple String variable, and trying to read the value in the constructor.
It's showing error: Null check operator used on a null value.
But when I try to read the value in the widget, it works, and doesn't show any error.
Here is the main.dart
void main() {
runApp(MaterialApp(
home: HomePage(),
));
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
var dummyText = "Dummy Text";
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => DPage(dummyText)));
},
child: Text('Press Me!'),
),
));
}
}
Here is the DPage where I'm trying to read variable dummyText.
class DPage extends StatefulWidget {
var dvariable;
DPage(this.dvariable);
#override
State<DPage> createState() => _DPageState();
}
class _DPageState extends State<DPage> {
_DPageState() {
print(widget.dvariable);
}
#override
Widget build(BuildContext context) {
return Container(child: Text(widget.dvariable));
}
}
if I comment the following line, which is in constructor -
print(widget.dvariable);
It runs without any problem. But if I try to access the widget.dvariable in the constructor, it throws error -
Null check operator used on a null value
How do I access this dvariable or value of "dummyText" in constructor? Any pointers will be greatly appreciated.
Note: I'm noob in dart/flutter.
You can use initState for this case. Constructor gets called before initState State and not ready to read widget class variables.
#override
void initState() {
super.initState();
print(widget.dvariable);
}
class DPage extends StatefulWidget {
var dvariable;
DPage(this.dvariable);
#override
State<DPage> createState() => _DPageState();
}
class _DPageState extends State<DPage> {
#override
void initState() {
super.initState();
print(widget.dvariable);
}
#override
Widget build(BuildContext context) {
return Container(child: Text(widget.dvariable));
}
}
Or
class DPage extends StatefulWidget {
var dvariable;
DPage(this.dvariable);
#override
State<DPage> createState() => _DPageState();
}
class _DPageState extends State<DPage> {
_DPageState() {
print(widget.dvariable);
}
#override
void initState() {
super.initState();
_DPageState();
}
#override
Widget build(BuildContext context) {
return Container(child: Text(widget.dvariable));
}
}
I have the following code, to get initial data for the screen and this SchedulerBinding seems to be a hack, but if I remove it, request data is lost.
I think it happens due to the fact widgets(streamBuilders etc.) are not yet built.
Any ideas how can I fix this?
Full screen code: https://gist.github.com/Turbozanik/7bdfc69b36fea3dd38b94d8c4fcdcc84
Full bloc code: https://gist.github.com/Turbozanik/266d3517a297b1d08e7a3d7ff6ff245f
SchedulerBining is not a hack,according to docs addPostFrame call callback only once and if you remove it your stream will never get the data
but you can call your stream loading in iniState
void initState(){
super.initState();
_mblock.loadSpotMock();
}
You can load your data asynchronously in the initState method, meanwhile you can show a loader or message. Once your data has loaded, call setState to redraw the widget.
Here is an example of this:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
createState() => new MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
String _data;
Future<String> loadData() async {
// Simulate a delay loading the data
await Future<void>.delayed(const Duration(seconds: 3));
// Return the data
return "This is your data!";
}
#override
initState() {
super.initState();
// Call loadData asynchronously
loadData().then((s) {
// Data has loaded, rebuild the widget
setState(() {
_data = s;
});
});
}
#override
Widget build(BuildContext context) {
if (null == _data) {
return Text("Loading...");
}
return Text(_data);
}
}
You can test it in https://dartpad.dartlang.org
It works like this:
initState will call loadData asynchronously, then the build method will draw the widget.
when loadData returns, the call to setState will redraw the widget.
Using StreamBuilder
The following example uses a StreamBuilder to show the data, once it's loaded:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
createState() => new MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
// Create a stream and execute it
final Stream<String> _myStream = (() async* {
// Simulate a delay loading the data
await Future<void>.delayed(const Duration(seconds: 3));
// Return the data
yield "This is your data!";
})();
#override
Widget build(BuildContext context) {
return StreamBuilder<String>(
stream: _myStream,
builder: (BuildContext context, s) {
String result;
if (s.hasError) {
result = "Error";
}
else {
if (s.connectionState == ConnectionState.done) {
result = s.data;
}
else {
result = "Loading...";
}
}
return Text(result);
}
);
}
}
Hope this helps :)
From the docs of addPostFrameCallback, I see that
Post-frame callbacks cannot be unregistered. They are called exactly
once.
and so I wonder if addPostFrameCallback can be called after the widget is disposed since it cannot be unregistered?
I think the _postFrameCallback's callback will be called after the widget is disposed.
View the Flutter source code:
firstly, Post-frame callbacks cannot be unregistered. They are called exactly once. event registered after widget disposed, but still can't unregister.
secondly, when a new frame comes down, Flutter framework just check callback whether null, and then call the callback directly.
void handleBeginFrame(Duration? rawTimeStamp) {
final List<FrameCallback> localPostFrameCallbacks =
List<FrameCallback>.from(_postFrameCallbacks);
_postFrameCallbacks.clear();
for (final FrameCallback callback in localPostFrameCallbacks)
_invokeFrameCallback(callback, _currentFrameTimeStamp!);
}
void _invokeFrameCallback(FrameCallback callback, Duration timeStamp, [ StackTrace? callbackStack ]) {
assert(callback != null);
callback(timeStamp);
}
So, the callback will be called.
But, you can't update the UI (setState) in the callback because widgets have already been disposed.
example:
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Test(),
);
}
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
bool isEnabled = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child:Listener(
onPointerDown: (event) {
setState(() {
isEnabled = true;
});
},
onPointerUp: (event) {
setState(() {
isEnabled = false;
});
},
child: Container(
height: 200,
width: 300,
color: Colors.red,
child: isEnabled ? Test1() : Text("null"),
),
),
),
);
}
}
class Test1 extends StatefulWidget {
#override
_Test1State createState() => _Test1State();
}
class _Test1State extends State<Test1> {
#override
Widget build(BuildContext context) {
return Text("hahah");
}
_onFrameStart(Duration duration) {
print("in _onFrameStart");
}
#override
void dispose() {
SchedulerBinding.instance.addPostFrameCallback(_onFrameStart);
super.dispose();
}
}
I am using stacked architecture in my project.
Here is my code
class InfoScreen extends StatelessWidget {
InfoViewModel viewModel;
#override
Widget build(BuildContext context) {
return ViewModelBuilder<InfoViewModel>.reactive(
builder: (context, model, child) => _buildUI(model),
viewModelBuilder: () => InfoViewModel());
}
_buildUI(InfoViewModel viewModel) {
return Scaffold(backgroundColor: Colors.white, body: MainScreen());
}
}
I am using the Stateless widget, So I can't use the didChangeDependencies() method to know the app state.
My Question is How do I handle app state in this screen?
any help or idea is appreciated. thanks in advance
You can implement didChangeDependencies() in your ViewModel.
For example:
class InfoViewModel extends BaseViewModel with WidgetsBindingObserver{
void initialise() {
WidgetsBinding.instance.addObserver(this);
}
#override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) async {
switch (state) {
case AppLifecycleState.resumed:
print('On Resume');
break;
case AppLifecycleState.inactive:
case AppLifecycleState.paused:
case AppLifecycleState.detached:
break;
}
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
}
Don't forget to call onModelReady: (model) => model.initialise() in your View widget.
We moved from Page1 to Page2 but now from Page2 we move back again to Page1 like this:
Navigator.of(context).pop();
How can we detect on Page1 that we went back?
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NextPage(),
),
).then((_){
// Here you will get callback after coming back from NextPage()
// Do your code here
});
In your Page1, When you push Page2 wait for it to pop
Future<void> _goToPage2() async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Page2(),
),
);
print("Page2 is popped");
}
Another solution, which is more verbose but also more efficient if you push a lot of Routes from the same Widget, would be to create your own NavigatorObserver.
1- Create your NavigatorObserver
final routeObserver = MyRouteObserver();
class MyRouteObserver extends NavigatorObserver {
final Set<RouteAware> _listeners = <RouteAware>{};
void subscribe(RouteAware routeAware) {
_listeners.add(routeAware);
}
void unsubscribe(RouteAware routeAware) {
_listeners.remove(routeAware);
}
#override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
for (var listener in _listeners) {
listener.didPop();
}
}
}
2- Add it to your MaterialApp
return MaterialApp(
navigatorObservers: [routeObserver],
...
3- Implement RouteAware in the Widget where you want to listen to Navigation events, and subscribe/unsubscribe to your routeObserver
class _TablePageState extends State<TablePage> implements RouteAware {
#override
void initState() {
super.initState();
routeObserver.subscribe(this);
}
#override
Widget build(BuildContext context) {
return Container();
}
#override
void dispose() {
routeObserver.unsubscribe(this);
super.dispose();
}
#override
void didPop() {
//This method will be called from your observer
}
#override
void didPopNext() {}
#override
void didPush() {}
#override
void didPushNext() {}
}