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();
}
Related
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’m following the example given on the Estimotes developers doc. Code is as follows
I'm facing an 'incompatible type error" when trying to pass the value to scanID variable.
package e.user.estimotetelemetry;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import com.estimote.coresdk.recognition.packets.EstimoteTelemetry;
import com.estimote.coresdk.service.BeaconManager;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private BeaconManager beaconManager;
private BeaconManager bm;
private String scanId;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
beaconManager = new BeaconManager(this);
beaconManager.setTelemetryListener(new BeaconManager.TelemetryListener() {
#Override
public void onTelemetriesFound(List<EstimoteTelemetry> telemetries) {
for (EstimoteTelemetry tlm : telemetries) {
Log.d("TELEMETRY", "beaconID: " + tlm.deviceId +
", temperature: " + tlm.temperature + " °C");
}
}
});
}
#Override protected void onStart() {
super.onStart();
beaconManager.connect(new BeaconManager.ServiceReadyCallback() {
#Override
public void onServiceReady() {
// scanId = beaconManager.startTelemetryDiscovery(); // This line shows up an incompatibility error as : incompatible types required java.lang.string found void
}
});
}
#Override
protected void onStop() {
super.onStop();
// beaconManager.stopTelemetryDiscovery(scanId); //incompatible types required java.lang.string found void
}
}
It’ll be great if someone could help with this. Thanks in advance!
The API for telemetry discovery changed slightly, and it looks like the docs haven't been updated accordingly—I've just fixed that.
In the most recent version of the SDK, startTelemetryDiscovery no longer returns a scanId, and stopTelemetryDiscovery no longer accepts one. You just start and stop the scan:
#Override protected void onStart() {
super.onStart();
beaconManager.connect(new BeaconManager.ServiceReadyCallback() {
#Override
public void onServiceReady() {
beaconManager.startTelemetryDiscovery();
}
});
}
#Override protected void onStop() {
super.onStop();
beaconManager.stopTelemetryDiscovery();
}
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
I have the following code in two different files:
#FXML
public void openFileMenu() throws IOException {
FXMLLoader loader = new FXMLLoader(getClass().getResource("FileMenu.fxml"), resourceBundle);
AnchorPane root = (AnchorPane) loader.load();
MenuController menuController = loader.getController();
if (toolbarStack.getWidth() < menuController.getContentWidth()) {
menuController.addManipulator();
}
ResizeListener.widthProperty.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observableValue, Number number, Number number2) {
if (toolbarStack.getWidth() < menuController.getContentWidth()) {
menuController.addManipulator();
} else {
menuController.removeManipulator();
}
}
});
}
and
package UI;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.animation.*;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.util.Duration;
public class MenuController implements Initializable {
#Override
public void initialize(URL url, ResourceBundle rb) {}
public void addManipulator() {
AnchorPane.setLeftAnchor(menuScrollPane, 50.0);
AnchorPane.setRightAnchor(menuScrollPane, 50.0);
leftButton = new Button("<");
leftButton.setId("leftButton");
leftButton.setPrefSize(50.0, 100.0);
AnchorPane.setLeftAnchor(leftButton, 0.0);
leftButton.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
slideTo(LEFT);
}
});
leftButton.setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
stopSlide();
}
});
rightButton = new Button(">");
rightButton.setId("rightButton");
rightButton.setPrefSize(50.0, 100.0);
AnchorPane.setRightAnchor(rightButton, 0.0);
rightButton.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
slideTo(RIGHT);
}
});
rightButton.setOnMouseReleased(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
stopSlide();
}
});
mainMenuPane.getChildren().addAll(leftButton, rightButton);
}
public void removeManipulator() {
mainMenuPane.getChildren().removeAll(leftButton, rightButton);
AnchorPane.setLeftAnchor(menuScrollPane, 0.0);
AnchorPane.setRightAnchor(menuScrollPane, 0.0);
}
private void slideTo(boolean direction) {
value.setValue(menuScrollPane.getHvalue());
double realSpeed = direction ? animationSpeed*(1-menuScrollPane.getHvalue()) : animationSpeed*menuScrollPane.getHvalue();
timeline = new Timeline();
KeyValue kv = direction ? new KeyValue(value, 1.0) : new KeyValue(value, 0.0);
KeyFrame frame = new KeyFrame(Duration.millis(realSpeed), kv);
timeline.getKeyFrames().add(frame);
timeline.play();
value.addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observableValue, Number oldValue, Number newValue) {
menuScrollPane.setHvalue(newValue.doubleValue());
}
});
}
private void stopSlide() {
timeline.stop();
timeline.getKeyFrames().removeAll(timeline.getKeyFrames());
}
public double getContentWidth() {
return menuContentPane.prefWidthProperty().doubleValue();
}
#FXML
private AnchorPane mainMenuPane;
#FXML
private AnchorPane menuContentPane;
#FXML
private ScrollPane menuScrollPane;
private Button leftButton;
private Button rightButton;
private Timeline timeline;
private DoubleProperty value = new SimpleDoubleProperty();
private final boolean LEFT = true;
private final boolean RIGHT = false;
private double animationSpeed = 1000.0;
}
The problem is that after adding Buttons by menuController.addManipulator(); it's impossible to remove them - menuController.removeManipulator(); doesn't work. But the most strange thing is that only mainMenuPane.getChildren().removeAll(leftButton, rightButton); doesn't work, at the same time AnchorPane.setLeftAnchor(menuScrollPane, 0.0); AnchorPane.setRightAnchor(menuScrollPane, 0.0); do everything they must. What is wrong?
I am trying to get my Blackberry application to display a custom modal dialog, and have the opening thread wait until the user closes the dialog screen.
final Screen dialog = new FullScreen();
...// Fields are added to dialog
Application.getApplication().invokeAndWait(new Runnable()
{
public void run()
{
Application.getUiApplication().pushModalScreen(dialog);
}
});
This is throwing an Exception which says "pushModalScreen called by a non-event thread" despite the fact that I am using invokeAndWait to call pushModalScreen from the event thread.
Any ideas about what the real problem is?
Here is the code to duplicate this problem:
package com.test;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
public class Application extends UiApplication {
public static void main(String[] args)
{
new Application();
}
private Application()
{
new Thread()
{
public void run()
{
Application.this.enterEventDispatcher();
}
}.start();
final Screen dialog = new FullScreen();
final ButtonField closeButton = new ButtonField("Close Dialog");
closeButton.setChangeListener(new FieldChangeListener()
{
public void fieldChanged(Field field, int context)
{
Application.getUiApplication().popScreen(dialog);
}
});
dialog.add(closeButton);
Application.getApplication().invokeAndWait(new Runnable()
{
public void run()
{
try
{
Application.getUiApplication().pushModalScreen(dialog);
}
catch (Exception e)
{
// To see the Exception in the debugger
throw new RuntimeException(e.getMessage());
}
}
});
System.exit(0);
}
}
I am using Component Package version 4.5.0.
Building on Max Gontar's observation that the Exception is not thrown when using invokeLater instead of invokeAndWait, the full solution is to implement invokeAndWait correctly out of invokeLater and Java's synchronization methods:
public static void invokeAndWait(final Application application,
final Runnable runnable)
{
final Object syncEvent = new Object();
synchronized(syncEvent)
{
application.invokeLater(new Runnable()
{
public void run()
{
runnable.run();
synchronized(syncEvent)
{
syncEvent.notify();
}
}
});
try
{
syncEvent.wait();
}
catch (InterruptedException e)
{
// This should not happen
throw new RuntimeException(e.getMessage());
}
}
}
Unfortunately, the invokeAndWait method cannot be overridden, so care must be used to call this static version instead.
Seems as though there's a bunch of code in there that's unnecessary.
public class Application extends UiApplication {
public static void main(String[] args)
{
new Application().enterEventDispatcher();
}
private Application()
{
final Screen dialog = new FullScreen();
final ButtonField closeButton = new ButtonField("Close Dialog");
closeButton.setChangeListener(new FieldChangeListener()
{
public void fieldChanged(Field field, int context)
{
Application.getUiApplication().popScreen(dialog);
}
});
dialog.add(closeButton);
// this call will block the current event thread
pushModalScreen(dialog);
System.exit(0);
}
}
Use this:
UiApplication.getUiApplication().invokeAndWait(new Runnable() {
public void run() {
pushScreen(new MyScreen());
}
});