Dispose method not called in flutter - flutter

I am facing an issue in which dispose method is not called after changing screen in flutter .First of all here is the source code.
class Game extends StatefulWidget {
Game({Key key, this.title}) : super(key: key);
final String title;
#override
_GameState createState() => new _GameState();
}
class _GameState extends State<Game> with SingleTickerProviderStateMixin {
final CrosswordController myController = CrosswordController();
var chewieController = null;
var videoPlayerController = null;
Widget makeVideoStreaming(){
videoPlayerController = VideoPlayerController.network("https://somelink.com");
chewieController = ChewieController(//paramtere here
);
}
#override
void initState() {
super.initState();
this.makeVideoStreaming();
_controller = AnimationController(vsync: this, duration: Duration(minutes: gameTime));
}
#override
void dispose(){
print('DISPOSE CALLED- GAME---');
videoPlayerController.dispose();
chewieController.dispose();
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _onBackPressed,
child: Scaffold(
key: _scaffoldKey,
drawer: NavigationDrawer(),
resizeToAvoidBottomPadding: false,
body://body here
),
);
}
}
In NavigationDrawer() i changes to some different route something like this.
onTap: () {
Navigator.pop(context); Navigator.pushNamed(context, '/edit_profile');
},
Above is just a part of code which is called after clicking on one of the item from drawer list item.
In GameState dispose method is not called why ?

Dispose method called when you remove screen from stack mean's that when you use navigator.pop() Or pushReplacement;

it's a known bug: https://github.com/flutter/flutter/issues/40940
even if you copy examples from official docs, it will not get called: https://flutter.dev/docs/cookbook/networking/web-sockets#complete-example
I've started a thread of flutter-dev to find out if something else should be used instead: https://groups.google.com/g/flutter-dev/c/-0QZFGO0p-g/m/bictyZWCCAAJ

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(),
);
}
}

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,
),
);
}
}

Best practice for calling an API or method at start point of Flutter App that needs context?

What is the best approach to calling an API or method when a screen wants to start in the Flutter app?
As I know in initState method, there is no context so I use didChangeDependencies method like below, but I always think there is a better way to tackle this issue. Any idea, snippet, or sample project link will be appreciated.
class HomeTabScreen extends StatefulWidget {
static String route = 'accountTab';
#override
_HomeTabScreenState createState() => _HomeTabScreenState();
}
class _HomeTabScreenState extends State<HomeTabScreen> {
late HomeTabViewModel homeTabVM;
var firstTime = true;
late TabController _controller;
#override
void initState() {
super.initState();
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (firstTime) {
homeTabVM = context.watch<HomeTabViewModel>();
homeTabVM.getUserData(context);
_controller = TabController(
length: homeTabVM.list.length,
vsync: this,
initialIndex: 1,
);
firstTime = false;
}
homeTabVM.listKey = GlobalKey();
homeTabVM.listItems.clear();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.transparent,
child: Center(
child: CircularProgressIndicator(),
),
));
}
}

Flutter back navigation doesnt trigger RouteAware didPop()

My goal is to track routes being presented and dismissed for analytics purposes like described on this article: https://medium.com/flutter-community/how-to-track-screen-transitions-in-flutter-with-routeobserver-733984a90dea
I am pushing a CupertinoPageRoute and passing a Widget to it that implements the RouteAware Mixin.
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
return CupertinoApp(
...
navigatorObservers: [routeObserver],
}
Pushing like so:
Navigator.of(context).push(
CupertinoPageRoute(builder: (context) => MyWidget()),
);
The Widget
class MyWidget extends StatefulWidget {
MyWidget({Key key}) : super(key: key);
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> with RouteAware {
#override
void didChangeDependencies() {
super.didChangeDependencies();
routeObserver.subscribe(this, ModalRoute.of(context));
}
#override
void dispose() {
routeObserver.unsubscribe(this);
super.dispose();
}
#override
void didPop() {
print('never called');
}
#override
void didPopNext() {
print('never called');
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
CupertinoNavigationBar(
middle: Text('42'),
),
],
),
);
}
}
The didPop() method is never called when popping a CupertinoPageRoute with the back button unless I push with rootNavigator = true which is not what I want.
How can I fix this?
EDIT: This behavior only happens when pushing routes from a CupertinoTabView, as it creates a new nested navigator.
Github Issue
If I understand correctly, the final solution shown in the Medium article does not extend every StatefulWidget with RouteAware. Instead, it extents its own implementation of a RouteObserver to get notified about every route change.
Using this approach, you should be able to insert your custom RouteObserver into both you main App as well as your CupertinoTabView to achieve what you need.

How can I pass parameters to listeners in Flutter?

I'm new in Flutter and I am following this official example about text fields: https://flutter.dev/docs/cookbook/forms/text-field-changes
There is an axample for listen to changes in the controller of a text field widget. Please note this fragment of code _MyCustomFormState
final myController = TextEditingController();
#override
void initState() {
super.initState();
myController.addListener(_printLatestValue);
}
_printLatestValue() {
print("Second text field: ${myController.text}");
}
If I have two fields and two controllers, I would like to have just one listener, and display some message depending on which controller called the method. I would like to do something like this:
final myController1 = TextEditingController();
final myController2 = TextEditingController();
#override
void initState() {
super.initState();
myController1.addListener(_printLatestValue('message1'));
myController1.addListener(_printLatestValue('message2'));
}
_printLatestValue(message) {
print("Second text field: ${myController.text + message}");
}
which is not possible because the method addListener() uses some called VoidCallback, which have no arguments. At least that is what I understood from the Flutter docs.
So, if it is possible, how can I achieve what I'm looking for?
You're almost correct, but not quite. You're free to pass in any arguments to the listener. However, those arguments need to come from somewhere else - TextEditingController does not supply any, and it does not expect any return values. In other words, the signature should be something like: () => listener(...).
So to answer your question, you're free to do something like the following to distinguish the controllers:
void initState() {
super.initState();
firstController.addListener(() => _printLatestValue('first'));
secondController.addListener(() => _printLatestValue('second'));
}
Full working example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Text controllers',
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final firstController = TextEditingController();
final secondController = TextEditingController();
void initState() {
super.initState();
firstController.addListener(() => _printLatestValue('first'));
secondController.addListener(() => _printLatestValue('second'));
}
#override
void dispose() {
firstController.dispose();
secondController.dispose();
super.dispose();
}
_printLatestValue(message) {
if (message == 'first') {
print('Received form first controller: ${firstController.text}');
} else {
print('Received from second controller: ${secondController.text}');
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Controllers', style: TextStyle(fontSize: 18)),
),
body: Center(
child: Column(
children: <Widget>[
TextField(controller: firstController,),
TextField(controller: secondController,)
],
),
),
);
}
}
Note that in this case, listener will only print the text from a TextField that was changed.