Firstly, my dropdown menu height was not being adjusted to the space left by the occupancy of the onscreen popup keyboard, which is now solved in my older questions. The solution made use of FocusNode
and other assisting codes to get my problem solved. But a new problem arose perhaps due to the implementation of the new code. My app allows navigation to a different page when the user click on the TextButton
and by clicking the back-arrow button, the user can come back to the previous main menu page.
The problem: When a user clicks the TextButton and go to the other page, and there if he does not click on the TextField
and clicks on the back-arrow, no exception occurs, and the user can easily move back and forth in this way. But when the user clicks the TextButton, goes to the next page, and clicks in the TextField
and then presses the back-arrow, the user returns to the main menu page but also an exception occurs at the same time. After this exception, the user is no longer able to select the TextButton option for navigation to any of the pages implemented using multiple TextButtons. The UI freezes. This is how we can replicate the exception for the codes shown below:
The following is the content of my main.dart
page:
import 'package:ask__widget_and_app_effective_height/my_home_page.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Screen__Menu(),
);
}
}
class Screen__Menu extends StatelessWidget {
const Screen__Menu({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blue,
title: Text("Demo App"),
),
body: ListView(
children: [
TextButton(
child: const Text("MyHomePage"),
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => MyHomePage()));
},
),
],
),
);
}
}
The following is the content of my my_home_page.dart
page:
import 'dart:ui';
import 'package:flutter/material.dart';
const List<String> list = <String>[
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
];
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey _key01 = GlobalKey();
final GlobalKey _key02 = GlobalKey();
late final TextEditingController firstController;
late Size screenSize;
double screenWidth = 0;
double screenHeight = 0;
double heightAppBar = 0;
double heightTextField = 0;
double heightKeyboard = 0;
double heightOthersCummulative = 0;
double heightAdjustment = 48;
double heightDropdownMenu = 0;
void setDropdownMenuHeight(
double heightAppBar, double heightTextField, double heightKeyboard) {
if (heightAppBar > 0 && heightTextField > 0) {
heightDropdownMenu = screenHeight -
heightAppBar -
heightTextField -
heightKeyboard -
heightOthersCummulative -
heightAdjustment;
// return heightDropdownMenu;
} else {
heightDropdownMenu = 150;
// return heightDropdownMenu;
}
}
late String dropdownValue = list.first;
String result = "";
String effective_available_screen_height = "";
String dimensions = "";
FlutterView view = WidgetsBinding.instance.platformDispatcher.views.first;
late Size size;
double width = 0;
double height = 0;
double height01 = 0;
final FocusNode _focusNode = FocusNode();
@override
void initState() {
super.initState();
firstController = TextEditingController();
_focusNode.addListener(_onFocusChange);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
setState(() {
heightAppBar = _key01.currentContext?.size?.height ?? -1;
heightTextField = _key02.currentContext?.size?.height ?? -1;
heightKeyboard = MediaQuery.of(context).viewInsets.bottom;
setDropdownMenuHeight(heightAppBar, heightTextField, heightKeyboard);
dimensions =
"KeyboardHeight=$heightKeyboard\nDropdownMenuHeight=$heightDropdownMenu\nAppBarHeight=${_key01.currentContext?.size?.height}\nTextFieldHeight=${_key02.currentContext?.size?.height}";
});
});
}
void _onFocusChange() {
Future.delayed(const Duration(milliseconds: 500), () {
setState(() {
heightKeyboard = MediaQuery.of(context).viewInsets.bottom;
setDropdownMenuHeight(heightAppBar, heightTextField, heightKeyboard);
});
});
}
@override
void dispose() {
_focusNode.removeListener(_onFocusChange);
_focusNode.dispose();
firstController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
screenSize = MediaQuery.of(context).size;
screenWidth = screenSize.width;
screenHeight = screenSize.height;
Size phSize = view.physicalSize;
double phHeight = phSize.height;
size = MediaQuery.of(context).size;
width = size.width;
height = size.height;
final padding = MediaQuery.of(context).viewPadding;
height01 = height - padding.top - padding.bottom;
effective_available_screen_height = "$height01";
// Height of Keyboard when onscreen (ohterwise zero):
double keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
return Scaffold(
appBar: AppBar(
key: _key01,
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text("Demo App"),
),
body: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Container(
padding: const EdgeInsets.all(5.0),
child: Column(
children: [
Container(
child: Row(
children: [
Expanded(
flex: 5,
child: Container(
padding: const EdgeInsets.all(5.0),
child: TextField(
key: _key02,
style: const TextStyle(
fontSize: 16,
),
controller: firstController,
keyboardType: TextInputType.number,
focusNode: _focusNode,
onChanged: (value) {
setState(() {
result = value;
/*
// The following gave error:
dimensions =
"${_key01?.currentContext.size.height}";
*/
dimensions =
"${_key01.currentContext?.size?.height}";
});
},
),
),
),
Expanded(
flex: 5,
child: Container(
padding: const EdgeInsets.all(5.0),
child: DropdownMenu<String>(
menuHeight: heightDropdownMenu,
// menuHeight: 300,
textStyle: const TextStyle(
fontSize: 16.0,
),
initialSelection: dropdownValue,
onSelected: (String? value) {
print(
"==========[DropdownMenu]$heightDropdownMenu");
print(
"========[DropdownMenu](kbdHt=)$heightKeyboard");
setState(() {
// codes here
});
},
dropdownMenuEntries: list
.map<DropdownMenuEntry<String>>((String value) {
return DropdownMenuEntry<String>(
value: value, label: value);
}).toList(),
),
),
),
],
),
),
Text("Result = $result"),
Text("Total physical height = $phHeight"),
Text("Total logical height = $height"),
Text("Onscreen Keyboard height = $keyboardHeight"),
Text(
"Working Height Available (logical) = $effective_available_screen_height"),
Text("Dimensions: $dimensions"),
],
),
),
),
);
}
}
UPDATE:
I also tried wrapping the Navigator.push in a try-catch block, and also implemented Navigator.pop using a FloatingActionButton. Also Navigator.pop is wrapped in try-catch block.
Navigator.push: (now) (This did not catch any exception sadly.)
try {
Navigator.push(context,
MaterialPageRoute(builder: (context) => MyHomePage()));
} catch (e) {
print("########## Caught exception: $e");
} finally {
print("########## Navigator.push completed!");
}
Navigator.pop: (now) (This also did not catch any exception. But the exception seems to occur when the previously stated exception-reproduction method is tried twice.)
FloatingActionButton(
child: Icon(Icons.arrow_back),
onPressed: () {
try {
Navigator.pop(context);
} catch (e) {
print("########## Caught exception: $e");
} finally {
print("########## Finished Navigator.pop!");
}
},
),
I have no idea what else to try, where code has gone wrong?
The error as described in debug console is about setting state in a widget that no longer exists. All you need to do is change
void _onFocusChange() {
Future.delayed(const Duration(milliseconds: 500), () {
setState(() {
heightKeyboard = MediaQuery.of(context).viewInsets.bottom;
setDropdownMenuHeight(heightAppBar, heightTextField, heightKeyboard);
});
});
}
to
void _onFocusChange() {
Future.delayed(const Duration(milliseconds: 500), () {
if(mounted) {
setState(() {
heightKeyboard = MediaQuery.of(context).viewInsets.bottom;
setDropdownMenuHeight(heightAppBar, heightTextField, heightKeyboard);
});
}
});
}
preferably always check if widget is mounted whenever you set state.
What happens exactly is that future starts the countdown when user clicks the back arrow after pressing textfield. But when countdown finishes the screen does not exist anymore- so the state cannot be set.