Flutter Do not show FutureBuilder error screen - flutter

I use the package flutter_native_splash to display a splash screen. During this time, I want to instantiate a Future and, when it's done, I want to remove the splash screen and build a widget thanks to a FutureBuilder. The problem is that the device displays the splash screen AND the ErrorScreen for few milliseconds (I don't want it) before building the widget.
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late Future<Metadata> futureMetadata;
#override
void initState() {
super.initState();
initialization();
}
void initialization() async {
futureMetadata = fetchMetadata();
FlutterNativeSplash.remove();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: FutureBuilder<Metadata>(
future: futureMetadata,
builder: (BuildContext context, AsyncSnapshot<Metadata> snapshot) {
if (snapshot.hasData) {
return NavBar(data: snapshot.data);
} else {
return const ErrorScreen();
}
}
)
);
}
}
FlutterNativeSplash.remove(); removes the splash screen.

because you want to remove the splashscreen after it's finished loading I think you want instead of
futureMetadata = fetchMetadata();
FlutterNativeSplash.remove();
do
futureMetadata = fetchMetadata();
await futureMetadata;
FlutterNativeSplash.remove();

Related

How to show a new screen for 10 seconds only: Flutter

I am trying to show a screen in flutter which should appear only for 10 seconds and then should disapper going back to previous screen. How this should be done in the correct manner in flutter. Is it possible to use timer.periodic in Flutter?
In the initstate of the screen that you wish to close add
Future.delayed(Duration(seconds: 10), (){
//Navigator.pushNamed("routeName");
Navigator.pop(context);
});
You can create new screen with Future.delayed inside initState
class NewPage extends StatefulWidget {
NewPage({Key? key}) : super(key: key);
#override
State<NewPage> createState() => _NewPageState();
}
class _NewPageState extends State<NewPage> {
#override
void initState() {
super.initState();
Future.delayed(Duration(seconds: 10)).then((value) {
Navigator.of(context).pop();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
);
}
}

Prevent FutureBuilder recalling future function every build

How to prevent FutureBuilder future function to be recalled on every build? In below case, the category.
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
Future<List<Category>> categories =
CategoryRepository().getCategories();
#override
Widget build(BuildContext context) {
return Center(
child: FutureBuilder(
future: categories,
builder: (BuildContext context, AsyncSnapshot<List<Category>> snapshot) {
if (snapshot.hasData) {
return SomeWidget()
} else {
return OtherWidget();
}
}
)
);
}
}
In the documentation, to prevent that, the future should be obtained before, i.e. initState(). I have tried to move the assignment of the categories to the initState() function. But the function still called when state change / rebuild.
class _HomeScreenState extends State<HomeScreen> {
late Future<List<Category>> categories;
#override
void initState() {
categories = CategoryRepository().getCategories();
super.initState();
}
}
Did you try this way -
class _HomeScreenState extends State<HomeScreen> {
Future<List<Category>> categories;
#override
void initState() {
if(categories==null) categories = CategoryRepository().getCategories();
super.initState();
}
}

How to listen changes inside TextController?

I am using GetX. I need to listen changes in TextController. The follow code do not work:
class Controller extends GetxController{
final txtList = TextEditingController().obs;
#override
void onInit() {
debounce(txtList, (_) {
print("debouce$_");
}, time: Duration(seconds: 1));
super.onInit();
}
}
Is does not print nothing when I am changing txtList value from UI. I suppose it's because it does not check text field inside txtList.
How to get it work?
You need to pass an RxInterface into debounce to do this via GetX. Just create an RxString and add a listener to the controller then pass the RxString into debounce.
class Controller extends GetxController {
final txtList = TextEditingController();
RxString controllerText = ''.obs;
#override
void onInit() {
txtList.addListener(() {
controllerText.value = txtList.text;
});
debounce(controllerText, (_) {
print("debouce$_");
}, time: Duration(seconds: 1));
super.onInit();
}
}
Then on any page in the app you can pass in that controller into the textfield and it'll print the value after the user stops typing for 1 second.
class Home extends StatelessWidget {
final controller = Get.put(Controller());
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: TextField(controller: controller.txtList), // this will print
),
);
}
}
And if you need that value for anything else it's also always accessible via controller.controllerText.value.
By TextEditingController.text, we can already get changing text input value so it does not need .obs.
To pass parameter for debounce, we should pass value itself : txtList.text. (see here: https://github.com/jonataslaw/getx/blob/master/documentation/en_US/state_management.md)
final txtList = TextEditingController(); // 1. here
#override
void onInit() {
debounce(txtList.text, (_) { // 2. here
print("debouce$_");
}, time: Duration(seconds: 1));
super.onInit();
}
This might work.
=================== added 11/21 ==================
Here's the example. I know the RxString variable seems a duplication for TextEditingController.text, but GetX's debounce function needs RxString type variable as a parameter. I tried to find more elegant way to do this, but I couldn't find anything. Please let me know if somebody knows a better way.
// in controller
late final TextEditingController textController;
final RxString userInput = "".obs;
#override
void onInit() {
super.onInit();
textController = TextEditingController();
userInput.value = textController.text;
textController.addListener(() {
userInput.value = textController.text;
}
);
debounce(userInput, (_) {
print("debouce$_");
}, time: Duration(seconds: 1));
}
check this snippet for example to listen to TextEditingController text change listener
import 'package:flutter/material.dart';
void main() async {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(),
darkTheme: ThemeData.dark(),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final TextEditingController controller = TextEditingController();
#override
void initState() {
super.initState();
controller.addListener(_printLatestValue);
}
void _printLatestValue() {
print('Second text field: ${controller.text}');
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: TextField(
controller: controller,
),
);
}
}

Flutter | how can i record the time user spend in my app in flutter?

I have developed and app and the client want me to store the total time spend by the user inside the app
how can I achieve that
I have tried using this App_usage package in flutter but its showing me Star Activity error
if you guys have any solution please let me know
thanks in advance :)
Have some variable that tracks the start time and end/ pause time of the app and persist the difference. You will have to hook that up to the app lifecycle to listen to events such as pausing/ resuming the app. (e.g. How to handle onPause/onResume in Flutter App?)
Something like this:
class AppLifecycleReactor extends StatefulWidget {
const AppLifecycleReactor({Key key}) : super(key: key);
#override
_AppLifecycleReactorState createState() => _AppLifecycleReactorState();
}
class _AppLifecycleReactorState extends State<AppLifecycleReactor>
with WidgetsBindingObserver {
DateTime startTime;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
startTime = DateTime.now();
}
if (state == AppLifecycleState.detached ||
state == AppLifecycleState.paused) {
var usageTime = DateTime.now().difference(startTime);
// do whatever with the usageTime
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: MyContent(),
);
}
}
like Chris Marx said, you can use the counter to store usage time. and to handle the sync operation to server, you can use shared preferenceq to store data and when the app launched again you do sync(update) with the server.
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(new HomePage());
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
String? docId;
addTime() async {
docId = await TimeHomePageUsage.addUserStartTime();
}
#override
void initState() {
// TODO: implement initState
super.initState();
addTime();
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
TimeHomePageUsage.addUserEndTime(docId);
}
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text('Home Page'),
),
);
}
}
class TimeHomePageUsage {
static Future<String?> addUserStartTime() async {
String? docId;
await FirebaseFirestore.instance
.collection('timeUsage')
.add({'startTime': DateTime.now().toString()})
.then((doc) => print("Start Time added ${docId = doc.id} "))
.catchError((error) => print("Failed to add Start Time: $error"));
return docId;
}
static Future<void> addUserEndTime(String? docId) async {
await FirebaseFirestore.instance
.collection('timeUsage')
.doc(docId)
.update({"endTime": DateTime.now().toString()})
.then((value) => print("End Time added "))
.catchError((error) => print("Failed to add End Time: $error"));
}
}

When AppLifeCycleState.detached gets called?

I've:
class _PageState extends State<Page> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
print('state = $state');
}
#override
Widget build(BuildContext context) => Scaffold();
}
AppLifeCycleState class has got 4 callbacks, 3 of them
- active
- paused
- resumed
Seems to work but detached never worked in any case.
I read the documentation but couldn't understand it in practical scenario, can anyone share a relevant code, when and where does it get called?
As the doc says
detached → const AppLifecycleState The application is still hosted on
a flutter engine but is detached from any host views.
When the application is in this state, the engine is running without a
view. It can either be in the progress of attaching a view when engine
was first initializes, or after the view being destroyed due to a
Navigator pop.
You can reproduce above issue on HomeScreen only when your home widgets go in the background(Press back button of android device)
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
didChangeAppLifecycleState(AppLifecycleState state) {
if (AppLifecycleState.paused == state) {}
print("Status :" + state.toString());
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Book'),
),
body: Center(
child: Text('Home Screen'),
),
);
}
}
You can produce above mention thing on other screens and have a call at detached, you can do by closing application programmatically on any click event of your widget
Android:
SystemChannels.platform.invokeMethod('SystemNavigator.pop');
iOS:
exit(0)
It's a bug. I've tests on both iOS and Android devices. You can produce detached state when you closed the app by pressing the back button or swiping on Android devices, it's not woking on iOS devices. Please follow this issue on this