Detect when user is not interacting the app in Flutter - flutter

I want to show some Screensaver type of screen when the user is not interacting the app for 5 minutes. So is anyone know how to achieve this kind of functionality in flutter.
import 'dart:async';
import 'package:flutter/material.dart';
const timeout = const Duration(seconds: 10);
const ms = const Duration(milliseconds: 1);
Timer timer;
void main() =>
runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
var home = MyHomePage(title: 'Flutter Demo Home Page');
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: home,
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _goToSecondScreen() {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => SecondPage()),
);
}
#override
Widget build(BuildContext context) {
return GestureDetector(child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(),
floatingActionButton: FloatingActionButton(
onPressed: _goToSecondScreen,
tooltip: 'Increment',
child: Icon(Icons.add),
),
), behavior:
HitTestBehavior.translucent, onTapDown: (tapdown) {
print("down");
if (timer != null) {
timer.cancel();
}
timer = startTimeout();
},);
}
startTimeout([int milliseconds]) {
var duration = milliseconds == null ? timeout : ms * milliseconds;
return new Timer(duration, handleTimeout);
}
void handleTimeout() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ScreenSaver()),
);
}
}
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Container();
}
}
class ScreenSaver extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new GestureDetector(child:Container(color: Colors.yellow,),
onTap: (){
Navigator.pop(context);
},
);
}
}
This is the sample code which I am trying to achieve the functionality. When the screen in active in Second screen its not working and the GestureDetector stops listening.

You can wrap your whole app in a GestureDetector with behavior:
HitTestBehavior.translucent to receive touch events while allowing widgets in your app also receiving these touch events.
You might also want to listen to keyboard events External keyboard in flutter support

Related

Flutter splash screen error - Navigator operation requested with a context that does not include a Navigator. How can I solve this error

Edit: (main.dart)
Added Sentry which actually starts the app
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = _sentryDSN;
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
// We recommend adjusting this value in production.
options.tracesSampleRate = _sentryTracesSampleRate;
options.attachStacktrace = true;
options.enableAppLifecycleBreadcrumbs = true;
},
appRunner: () => runApp(const SplashScreen()),
);
// or define SENTRY_DSN via Dart environment variable (--dart-define)
}
New to flutter, creating a splash screen to an app that was built with MaterialApp but getting an error. HOw can I solve this without a onPress function
Error:
Exception has occurred.
FlutterError (Navigator operation requested with a context that does not include a Navigator.
The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.)
import 'package:flutter/material.dart';
import 'package:loopcycle/screens/loopcycle_main.dart';
class SplashScreen extends StatefulWidget {
const SplashScreen({Key? key}) : super(key: key);
#override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
_navigateToMainApp();
}
void _navigateToMainApp() async {
await Future.delayed(const Duration(milliseconds: 2000), () {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const LoopcycleMainApp()));
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Builder(
builder: (context) => const Center(
child: Text("test"),
)),
);
}
}
Thank you in advance.
EDIT: I changed the solution after you provided more information about the code.
This error is happening because you are using a context that does not have a Navigator in it, this is happening probrably because the widget that you are getting the context is parent of the MaterialApp() widget, to solve it you should create another widget that is a child of the MaterialApp() instead of using the parent widget, let me give you an example instead:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: GestureDetector(
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SomeWidget(),
),
),
child: Container(
height: 300,
width: 300,
color: Colors.red,
),
),
);
}
}
This may give an error because you are using the context of a widget that is the parent of the MaterialApp() widget, to solve it just create another widget that is a child of MaterialApp().
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: AnotherWidget(),
);
}
}
class AnotherWidget extends StatelessWidget {
const AnotherWidget({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => SomeWidget(),
),
),
child: Container(
height: 300,
width: 300,
color: Colors.red,
),
),
);
}
}
I was playing with your code, and fixed it for you, and there are basically two ways to solve it, you can create a MaterialApp() before calling the SplashScreen() in the runApp() function like so:
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:loopcycle/screens/loopcycle_main.dart';
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = _sentryDSN;
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
// We recommend adjusting this value in production.
options.tracesSampleRate = _sentryTracesSampleRate;
options.attachStacktrace = true;
options.enableAppLifecycleBreadcrumbs = true;
},
appRunner: () => runApp(
const MaterialApp(
home: SplashScreen(),
),
),
);
// or define SENTRY_DSN via Dart environment variable (--dart-define)
}
class SplashScreen extends StatefulWidget {
const SplashScreen({Key? key}) : super(key: key);
#override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
_navigateToMainApp();
}
void _navigateToMainApp() async {
await Future.delayed(const Duration(milliseconds: 2000), () {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const LoopcycleMainApp()));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (context) => const Center(
child: Text("test"),
)),
);
}
}
Or you can create an intermediate widget to hold the MaterialApp() and then inside this widget you can call SplashScreen(), like so:
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:loopcycle/screens/loopcycle_main.dart';
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = _sentryDSN;
// Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
// We recommend adjusting this value in production.
options.tracesSampleRate = _sentryTracesSampleRate;
options.attachStacktrace = true;
options.enableAppLifecycleBreadcrumbs = true;
},
appRunner: () => runApp(const MyApp()),
);
// or define SENTRY_DSN via Dart environment variable (--dart-define)
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: SplashScreen(),
);
}
}
class SplashScreen extends StatefulWidget {
const SplashScreen({Key? key}) : super(key: key);
#override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
_navigateToMainApp();
}
void _navigateToMainApp() async {
await Future.delayed(const Duration(milliseconds: 2000), () {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const LoopcycleMainApp()));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (context) => const Center(
child: Text("test"),
)),
);
}
}
In this second solution, the intermediate widget is the MyApp() widget, and in my opinion, I consider this solution as being the best one for your problem, because if you ever wanted to load a different screen based on the different states, for example:
If a user is signed in you load a home page, and if a user is not signed in you load a sign up page.
Doing this, or anything similar is much easier when you have this intermediate widget that holds the MaterialApp(), and you can even create some logic to integrate the Splash Screen too, but I don't know what you are trying to achieve, so pick the solution you find the best for your problem.

Flutter: does scrollController.position.isScrollingNotifier need to remove listener on dispose? Why's it crashing my app?

I am able to demonstrate my issue with this simple code below.
Basically, I am adding listener to both the ScrollController and scrollController.position.isScrollingNotifier in the init. The scrollController.position.isScrollingNotifier allows me to listen for scrolling start and stopped events. It works fine.
Now when I tap a list tile, it navigates to a new screen (same widget as previous).
However, when I navigate back to the previous screen, the app crashes with error:
_AssertionError ('package:flutter/src/widgets/scroll_controller.dart': Failed assertion: line 107 pos 12: '_positions.isNotEmpty': ScrollController not attached to any scroll views.)
The crash is occurring at the removeListener line in the dispose:
scrollController.position.isScrollingNotifier.removeListener(handleScrollStartStop);
If I comment this line out, then the app works fine.
I thought I am supposed to remove listeners in dispose if I added them? Then why the crash?
What am I doing wrong?
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(
viewNumber: 0,
),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.viewNumber});
final int viewNumber;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late ScrollController scrollController = ScrollController();
#override
void initState() {
print("initState GODVC");
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
scrollController.addListener(handleScrolled);
scrollController.position.isScrollingNotifier.addListener(handleScrollStartStop);
});
}
void handleScrollStartStop() {
// print("handleScrollStartStop");
if (!scrollController.position.isScrollingNotifier.value) {
print('Scroll stopped');
} else {
print('Scroll started');
}
}
void handleScrolled() {
print("handleScrolled: $mounted, ${scrollController.offset}");
}
#override
void dispose() {
print("dispose");
scrollController.position.isScrollingNotifier.removeListener(handleScrollStartStop);
print("disposed GODVC 2");
scrollController.removeListener(handleScrolled);
print("disposed GODVC 3");
scrollController.dispose();
print("disposed GODVC 4");
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("${widget.viewNumber}"),
leading: Navigator.canPop(context)
? MaterialButton(
padding: EdgeInsets.zero,
onPressed: () {
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
},
child: const Icon(
Icons.chevron_left,
size: 45,
color: Colors.white,
),
)
: null,
),
body: ListView.builder(
controller: scrollController,
itemBuilder: ((context, index) {
return ListTile(
title: Text("$index. Something"),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => MyHomePage(
viewNumber: widget.viewNumber + 1,
),
),
);
},
);
}),
),
);
}
}

How to measure time of mounted widget in flutter?

I have a simple StopWatchController which i want to use to measure the time it takes to to mount SecondPage when clicking on the button next page in MyHomePage. I am not sure how i can achieve a succesfull return of the time between clicking and mounting. The return is always the start time when i debugPrint. How can i achieve a succesfull stopWatch.elapsed?
full code:
import 'package:flutter/material.dart';
class StopWatchController {
var stopWatch = Stopwatch();
stopWatchHandler(timer) {
if (timer == "start") {
stopWatch.start();
} else if (timer == "stop") {
stopWatch.stop();
debugPrint(stopWatch.elapsed.toString());
return stopWatch.elapsed.toString();
}
}
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: ElevatedButton(
child: Text("next page"),
onPressed: (){
Navigator.of(context)
.push(
MaterialPageRoute(
builder: (context) =>
const SecondPage()));
StopWatchController().stopWatchHandler("start");
}
)
),
);
}
}
class SecondPage extends StatefulWidget {
const SecondPage({
Key? key,
}) : super(key: key);
#override
State<SecondPage> createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
#override
initState(){
if (this.mounted == true) {
StopWatchController().stopWatchHandler("stop");
}
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
),
body: const Center(
child: Text("")
),
);
}
}
Edit
It debugPrint: 0:00:00.000000 when i press on next page
Try this in your stopWatchHandler(timer) function
stopWatchHandler(timer) {
if (timer == "start") {
stopWatch.start();
} else if (timer == "stop") {
stopWatch.stop();
debugPrint(stopWatch.elapsed.inMilliseconds.toString());
return "executed in ${stopwatch.elapsed.inMilliseconds}ms"; //<=== change-here
}
}

Why my Flutter Scaffold back button always return to root?

If I have stacks of screens: A -> B -> C, whethen I press back button on the simulator or press back button on the Scaffold's back button on C, I always returned to A, not B. Why? And how to fix it? I searched on the
Here's how I push and pop. These code are simplified to only show the push/pop and screen build functions.
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: UserListPage()
);
}
}
class UserListPage extends StatefulWidget {
#override
_UserListPageState createState() => _UserListPageState();
}
class _UserListPageState extends State<UserListPage> {
...
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("User list")),
body: Builder(builder: (context) {
InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SessionListPage(userId: users[index].id)
),
child: ...
})
}),
);
}
...
}
class SessionListPage extends StatefulWidget {
final int userId;
SessionListPage({ this.userId }) : super();
#override
_SessionListPageState createState() => _SessionListPageState();
}
class _SessionListPageState extends State<SessionListPage> {
...
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Session list")),
body: Builder(builder: (context) {
InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => DrawingPage(userId: widget.userId, sessionId: sessions[index].id)
),
child: ...
})
}),
);
}
...
}
class DrawingPage extends StatefulWidget {
final int userId;
final int sessionId;
DrawingPage({ this.userId, this.sessionId }) : super();
#override
_State createState() => _State();
}
class _State extends State<DrawingPage> {
#override
Widget build(BuildContext context) {
return Scaffold(appBar: AppBar(title: Text("Enter the Number")),
body: Builder(builder: (context) {
InkWell(
onTap: () { Navigator.pop(context); } // here it returns straight to UserListPage instead of SessionListPage
child: ...
})
}
));
}
}
One thing that I noticed is that for the context that was supplied to push and pop, is from Builder instead of the context supplied from Widget build function. Does it have any impact?
Please remove super(); from all places.
Young, It's working fine. Please check below link
Code

Async Listview update in flutter

I have created a program which displays a list view in build method and in init I have a async method.
That async method after 3 seconds adds an element in list and try to set State.
It is not working. My code is as follows.
calling async function in init may be wrong, i want to show the List view then make an async http call and then update the list view. and this should work even after push and pop.
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> europeanCountries = [
'Albania',
'Andorra',
'Armenia',
'Austria',
'Azerbaijan',
'Belarus',
'Belgium',
'Bosnia and Herzegovina'
];
int _counter = 0;
void _incrementCounter() async {
const ThreeSec = const Duration(seconds: 3);
Timer(ThreeSec, () {
europeanCountries.insert(0, "Testing");
print(europeanCountries);
});
setState(() {
_counter++;
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
_incrementCounter();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: _myListView(context),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
Widget _myListView(BuildContext context) {
// backing data
return ListView.builder(
itemCount: europeanCountries.length,
reverse: true,
itemBuilder: (context, index) {
return ListTile(
title: Text(europeanCountries[index]),
);
},
);
}
}
Use timeout method handle and call setState method inside that method like following way
Timer(ThreeSec, () {
europeanCountries.insert(0, "Testing");
print(europeanCountries);
setState(() {
_counter++;
});
});