Passing state down to widgets is easy. I have a StatefulWidget that contains an animation with its controller. I need to be able to trigger the animation from another widget higher in my widget tree.
My MainApp should trigger the animation using a button.
As I understand AnimationController only has an imperative API. I can call controller.forward() or controller.reverse(). But to do this I need to expose the controller to my MainApp.
What I currently do is to keep a global variable of my state around.
class MainApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
...
body: new LogoWidget(),
);
}
_startAnimation() {
_state.restartAnimation();
}
}
_LogoWidgetState _state; // yuk!
class LogoWidget extends StatefulWidget {
_LogoWidgetState createState() {
_state = _LogoWidgetState();
return _state;
}
}
class _LogoWidgetState extends State<LogoWidget>
with SingleTickerProviderStateMixin {
Animation<double> animation;
AnimationController controller;
restartAnimation() {
controller.value == 1.0 ? controller.reverse() : controller.forward();
}
...
}
(full sourcecode here)
What is a better way to deal with this?
You don't need _LogoWidgetState _state; // yuk! out in the middle of nowhere, but you can try:
create LogoWidget _myBody = LogoWidget(), and use that for your body:
similarily, apply with final _LogoWidgetState _state = _LogoWidgetState()
then call it as _myBody._state.restartAnimation()
Your sample, modified:
class MainApp extends StatelessWidget {
LogoWidget _myBody = LogoWidget(); //<---
#override
Widget build(BuildContext context) {
return new Scaffold(
...
body: _myBody, //<---
);
}
_startAnimation() {
_myBody._state.restartAnimation(); //<---
}
}
class LogoWidget extends StatefulWidget {
final _LogoWidgetState _state = _LogoWidgetState(); //<---
_LogoWidgetState createState() {
return _state;
}
}
But if you think _myBody._state.restartAnimation() is too long, you can shorten it with:
class LogoWidget extends StatefulWidget {
final _LogoWidgetState _state = _LogoWidgetState(); //<---
void restartAnimation() { //<---
_state.restartAnimation();
}
_LogoWidgetState createState() {
return _state;
}
}
Then just use _myBody.restartAnimation()
Here's some relevant posts:
call method in one stateful widget from another stateful widget - Flutter
Flutter: Call a function on a child widget's state
Related
The print functions should be called according to the lifecycle transitions, but none of them are being called. To test this, I'm running the app in debug mode and moving it to the background/foreground (i.e. changing to another app and then returning to this app).
What am I doing wrong?
import 'package:flutter/material.dart';
class StopwatchVw extends StatefulWidget {
const StopwatchVw({Key? key}) : super(key: key);
#override _StopwatchVwState createState() => _StopwatchVwState();
}
class _StopwatchVwState extends State<StopwatchVw> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
}
#override
Future<void> didChangeAppLifecycleState(AppLifecycleState state) {
print('Changed');
switch (state) {
case AppLifecycleState.inactive:
print("Inactive");
break;
case AppLifecycleState.paused:
print("Paused");
break;
case AppLifecycleState.resumed:
print("Resumed");
break;
case AppLifecycleState.detached:
print("Suspending");
break;
}
}
#override
void dispose() {
WidgetsBinding.instance!.removeObserver(this);
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(body: Text('HEY'));
}
This is my main.dart:
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:clocker/stopwatch_vw.dart';
import 'package:flutter/material.dart';
import 'package:flutter_acrylic/flutter_acrylic.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Window.initialize();
await Window.hideWindowControls();
runApp(const MyApp());
doWhenWindowReady(() {
const initialSize = Size(350, 200);
appWindow
..size = initialSize
..minSize = initialSize
..maxSize = initialSize
..alignment = Alignment.bottomRight
..show();
});
Window.setEffect(
effect: WindowEffect.acrylic,
color: const Color.fromARGB(29, 250, 250, 227),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MoveWindow(child: const StopwatchVw())
);
}
}
Flutter currently doesn't support LifeCycle events on the desktop, But there is an existing issue which is being tracked here #30735.
Currently, you can determine the status of your application with the window_manager package
class Windows extends StatefulWidget {
const Windows({super.key});
#override
State<Windows> createState() => _WindowsState();
}
class _WindowsState extends State<Windows> with WindowListener {
#override
void onWindowClose() {
// do something
}
#override
void onWindowFocus() {
// do something
}
#override
void onWindowMinimize() {
// do something
}
#override
void initState() {
windowManager.addListener(this);
super.initState();
}
#override
void dispose() {
windowManager.removeListener(this);
super.dispose();
}
My application has a stateful widget which is the profile area, I would like to display an animated icon indicating to the user that is possible to scroll the screen, I want to use the animated icon, how to auto animate an AnimatedIcon as soon the Profile screen loads, thanks.
Obs.: The play_pause is just a placeholder for an animated icon
import 'package:flutter/material.dart';
void main() {
runApp(Profile());
}
class Profile extends StatefulWidget {
#override
ProfileState createState() {
return ProfileState();
}
}
class ProfileState extends State<Profile> with TickerProviderStateMixin {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: AnimatedIcon(
progress: AnimationController(
vsync: this,
duration: Duration(milliseconds: 400),
reverseDuration: Duration(milliseconds: 400),
),
icon: AnimatedIcons.play_pause,
),
)
)
);
}
}
You have to create an instance of AnimationController and initialize it on the initState. Then you can start the animation by calling animateTo method.
import 'package:flutter/material.dart';
void main() {
runApp(Profile());
}
class Profile extends StatefulWidget {
#override
ProfileState createState() {
return ProfileState();
}
}
class ProfileState extends State<Profile> with TickerProviderStateMixin {
AnimationController _animationController;
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 400),
);
_animationController.animateTo(1.0);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: AnimatedIcon(
progress: _animationController,
icon: AnimatedIcons.play_pause,
),
),
),
);
}
}
you could do that using the animationController
_animationController.forward(); /// to start the animation
You can start your animation after the build method executes by calling it like this.
#override
void initState(){
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){
_animationController.forward();
});
}
I need to start an animation of a child widget from a parent widget. How can I do this?
I've tried giving the parent the controller, but then how do you replace vsync: this?
This is the basic code (I haven't actually tested this code yet, but I shows what I mean):
import 'package:flutter/material.dart';
class ParentWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
ChildText(),
FlatButton(
child: Text('start the animation'),
onPressed: () {
// start the animation!!!????????
},
)
],
);
}
}
class ChildText extends StatefulWidget {
#override
_ChildTextState createState() => _ChildTextState();
}
class _ChildTextState extends State<ChildText> with TickerProviderStateMixin {
AnimationController _controller;
Animation _animation;
#override
void initState() {
super.initState();
// actual animation is much more complex, this is just a random demo
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 2));
_animation = Tween(begin: -1.0, end: 100.0).animate(CurvedAnimation(
parent: _controller,
curve: Curves.fastOutSlowIn,
));
}
#override
Widget build(BuildContext context) {
return Transform.translate(
offset: Offset(0, _animation.value),
child: Text('Text with fancy animation'));
}
}
You can try this:
class ParentWidget extends StatefulWidget {
#override
_ParentWidget createState() => _ParentWidget();
}
class _ParentWidget extends State<ParentWidget> with TickerProviderStateMixin {
AnimationController _controller;
#override
void initState() {
super.initState();
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 2));
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
ChildText(_controller),
FlatButton(
child: Text('start the animation'),
onPressed: () {
// start the animation!!!
_controller.forward();
},
)
],
);
}
}
class ChildText extends StatefulWidget {
ChildText(this._controller);
final AnimationController _controller;
#override
_ChildTextState createState() => _ChildTextState();
}
class _ChildTextState extends State<ChildText> with TickerProviderStateMixin {
Animation _animation;
#override
void initState() {
super.initState();
_animation = Tween(begin: -1.0, end: 100.0).animate(CurvedAnimation(
parent: widget._controller,
curve: Curves.fastOutSlowIn,
));
}
#override
Widget build(BuildContext context) {
return Transform.translate(
offset: Offset(0, _animation.value),
child: Text('Text with fancy animation'));
}
}
I want to run a function every time the page changes in my Flutter application.
Ideally, I don't want to call this function in initState of every page, as sometimes people can forget to add the call in a new page.
Think of it as middleware - be basically before the page loads etc, some code needs to run.
Updated code for review
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:myapp/pages/login_page.dart';
import 'package:myapp/pages/dashboard_page.dart';
import 'package:myapp/styles/constants.dart';
import 'package:myapp/services/auth_service.dart';
Future<void> main() async {
// create a auth service instance
AuthService authService = AuthService(secureStorage: FlutterSecureStorage());
bool isLoggedIn = await authService.isUserLoggedIn();
// run the app
runApp(MyApp(
isLoggedIn: isLoggedIn,
));
}
class MyApp extends StatefulWidget {
final bool isLoggedIn;
MyApp({this.isLoggedIn});
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with RouteAware {
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
#override
void didChangeDependencies() {
super.didChangeDependencies();
routeObserver.subscribe(this, ModalRoute.of(context));
}
#override
void dispose() {
routeObserver.unsubscribe(this);
super.dispose();
}
#override
void didPush() {
print('didPush');
}
#override
void didPopNext() {
print('didPopNext');
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App NAME',
theme: ThemeData(
primarySwatch: Colors.green,
primaryColor: kPrimeColour,
),
home: widget.isLoggedIn == true ? DashboardPage() : LoginPage(),
navigatorObservers: [routeObserver],
);
}
}
You can use implementation method didChangeDependencies this function called after initState,
flutter doc :
Subclasses rarely override this method because the framework always calls build after a dependency changes. Some subclasses do override this method because they need to do some expensive work (e.g., network fetches) when their dependencies change, and that work would be too expensive to do for every build.
Link
#override
void didChangeDependencies() {
super.didChangeDependencies();
// set your stuff here
}
You can use inheritance
abstract class MyState<T extends StatefulWidget> extends State {
#override
void initState() {
super.initState();
//YOUR CHANGE PAGE METHOD HERE
}
}
class YellowBird extends StatefulWidget {
const YellowBird({ Key key }) : super(key: key);
#override
_YellowBirdState createState() => _YellowBirdState();
}
class _YellowBirdState extends MyState<YellowBird> {
#override
Widget build(BuildContext context) {
return Container(color: const Color(0xFFFFE306));
}
}
I have widget. I would like to fire an event as follow:
fireEvent(new IndicatorStartEvent("Message"));
But it dosn't work.
Normally I use Presenter for this (GWTP), but now I just would like to have regular widget:
public class FileUploadWidget extends Composite {
MaterialFileUploader uploader = new MaterialFileUploader();
#Inject
public FileUploadWidget(String triggerId, EventBus eventBus) {
super();
initWidget(uploader);
Window.alert("TEST Start");
fireEvent(new IndicatorStartEvent("Message"));
}
}
Here is event code:
public class IndicatorStartEvent extends GwtEvent<IndicatorStartEvent.IndicatorHandler> {
public static Type<IndicatorHandler> TYPE = new Type<IndicatorHandler>();
public interface IndicatorHandler extends EventHandler {
void onIndicatorProgressStart(IndicatorStartEvent event);
}
public interface IndicatorHandlers extends HasHandlers {
HandlerRegistration addStartIndicatorHandler(IndicatorHandler handler);
}
private final String message;
public IndicatorStartEvent(final String message) {
this.message = message;
}
public static Type<IndicatorHandler> getType() {
return TYPE;
}
#Override
protected void dispatch(final IndicatorHandler handler) {
handler.onIndicatorProgressStart(this);
}
#Override
public Type<IndicatorHandler> getAssociatedType() {
return TYPE;
}
public String getMessage() {
return this.message;
}
}
This is my app presenter that handle the event:
public class AppPresenter extends TabContainerPresenter<AppPresenter.MyView, AppPresenter.MyProxy> implements AppUiHandlers
, IndicatorStartEvent.IndicatorHandler {
#ProxyStandard
public interface MyProxy extends Proxy<AppPresenter> {}
public interface MyView extends TabView, HasUiHandlers<AppUiHandlers> {}
#Override
protected void onBind() {
super.onBind();
addRegisteredHandler(IndicatorStartEvent.getType(), this);
}
public void onAsyncCallFail(AsyncCallFailEvent event) {
// fireEvent is executed from: com.gwtplatform.mvp.client;PresenterWidget
fireEvent(new IndicatorStartEvent("Firing message"));
}
#Override
public void onIndicatorProgressStart(IndicatorStartEvent event) {
MaterialToast.fireToast("Indicator start: " + event.getMessage());
}
}
If I fire this event from f.e.: AppPresenter (code above), or GwtRESTY filter/callback ass follow:
class ProgressIndicatorFilter implements DispatcherFilter {
private AssistedInjectionFactory factory;
private EventBus eventBus;
#Inject
public ProgressIndicatorFilter(AssistedInjectionFactory factory, EventBus eventBus) {
this.factory = factory;
this.eventBus = eventBus;
}
#Override
public boolean filter(Method method, RequestBuilder builder) {
builder.setCallback(factory.createProgressIndicatorCallback(method));
eventBus.fireEvent(new IndicatorStartEvent("Rest-Gwt Comunication started"));
return true;
}
}
It work as expected. But in those working examples it use com.google.web.bindery.event.shared;EventBus
The firing event doesnt work from widget, where is used:
com.google.gwt.event.shared;HandlerManager;Bus class. This class Bus extends com.google.web.bindery.event.shared.SimpleEventBus which extends the proper EventBus class from com.google.web.bindery.event.shared;EventBus.
So the widget's method fireEvent() use other EventBus.
Can anyone help me with this?
I've red official and this instruction:
http://blog.arcbees.com/2015/04/01/gwt-platform-event-best-practices-revisited/ but no luck so far. Please help.
It does not work because your FileUploadWidget uses it's own EventBus and not GWTP one that is also used in all of your Presenters.
There are two solutions:
Don't use fireEvent(new IndicatorStartEvent("Message")) but use eventBus.fireEvent(new IndicatorStartEvent("Message")) on the injected EventBus inside of your Widget.
Add the IndicatorStartEvent handler to your FileUploadWidget directly instead of using addRegisteredHandler on your Presenter.
I prefer solution 2:
public class FileUploadWidget extends Composite {
MaterialFileUploader uploader = new MaterialFileUploader();
#Inject
public FileUploadWidget(String triggerId) {
super();
initWidget(uploader);
Window.alert("TEST Start");
fireEvent(new IndicatorStartEvent("Message"));
}
}
In the Presenter or to be precise the View which uses your FileUploadWidget, you add a handler directly to the FileUploadWidget:
public class UploadView extends ViewWithUiHandlers<UploadUiHandlers> implements UploadPresenter.MyView,IndicatorStartEvent.IndicatorHandler {
#UiField
FileUploadWidget uploadWidget;
#Inject
public UploadView(final Binder binder) {
widget = binder.createAndBindUi(this);
uploadWidget.addHandler(new IndicatorStartEvent.Handler(),this);
}
public void onIndicatorProgressStart(IndicatorStartEvent event) {
MaterialToast.fireToast("Indicator start: " + event.getMessage());
}
}