Reputation: 2644
I have a problem with Flutter reporting overflow problem when keyboard is opened up. This is how my view looks and behaves when keyboard is opened up or when form or fields are validated:
Is there a way to have top part ("Large title" and "lorem ipsum") widgets automatically resized when this happens so form fields and submit button are visible?
This happens in view where the elements are positioned within Column
widget.
I am already aware of resizeToAvoidBottomInset
but I do not want to use it since I don't want to obstruct any widgets below the keyboard.
I also know that I can use SingleChildScrollView
but I also do not want to use it since I would like the "Submit" button to be visible when keyboard is up.
You can look at gist to see this exact code:
import 'package:flutter/material.dart';
import 'dart:math';
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(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _hasError = false;
bool _usernameHasError = false;
bool _passwordHasError = false;
_handleFormSubmission() {
final rand = Random();
setState(() {
_hasError = rand.nextBool();
});
print('Sumbmitted');
}
_handleUsernameChange(String value) {
setState(() {
_usernameHasError = value.isEmpty;
});
}
_handlePasswordChange(String value) {
setState(() {
_passwordHasError = value.isEmpty;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: const EdgeInsets.all(10.0).add(const EdgeInsets.only(top: 30.0)),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
child: Column(children: [
Text('Large title',
style: Theme.of(context).textTheme.headline2),
Text(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, '
'sed do eiusmod tempor incididunt ut labore et dolore magna'
' aliqua. Ut enim ad minim veniam, quis nostrud exercitation '
'ullamco laboris nisi ut aliquip ex ea commodo consequat.',
style: Theme.of(context).textTheme.headline5),
]),
),
Container(
padding: const EdgeInsets.only(top: 10.0),
child: Column(children: [
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(bottom: 20.0),
child: Text('Additional information is placed here')),
TextFormField(
decoration: InputDecoration(
labelText: 'Username',
errorText: _usernameHasError ? 'Invalid username' : null),
onChanged: _handleUsernameChange,
),
TextFormField(
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
errorText: _passwordHasError ? 'Invalid password' : null),
onChanged: _handlePasswordChange,
),
_hasError
? Container(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error,
color: Theme.of(context).errorColor),
Text('An error occurred!',
style: TextStyle(
color: Theme.of(context).errorColor))
]))
: Container()
]),
),
Container(
padding: const EdgeInsets.only(top: 10.0),
child: Column(children: [
Container(
width: double.infinity,
child: ElevatedButton(
child: Text('Submit'),
onPressed: _handleFormSubmission,
)),
])),
]),
));
}
}
When you look at my GIF you can also see that keyboard being up is not the only thing that can resize the view, there's also error messages that can pop up.
I think one good solution would be to dynamically resize the text "Large title" and "Lorem ipsum" text on the top to make space for the form and the button.
I'm not sure if there's a reliable way to detect when keyboard is up but I've read about checking MediaQuery.of(context).viewInsets.bottom
in this question. When keyboard would open up I could resize the text (or hide it). Is this viable solution?
Or perhaps there's some layout that I'm not aware of? It'd be great to animate both widgets on top so the would fade away and/or decrease in size (maybe AnimatedContainer
)? That would still require to programmatically check for opened keyboard.
Any advice is appreciated!
Upvotes: 0
Views: 993
Reputation: 2644
I considered Neela's answer and then proceeded to experiment with some different approaches. One that I found sufficient enough was to just hide the lorem ipsum text since in my context it's not important when form field is focused.
I used flutter_keyboard_visibility plugin to detect when keyboard is up and then conditinoally removed the text widget.
Full code (also in this gist):
import 'package:flutter/material.dart';
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
import 'dart:math';
import 'dart:async';
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(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _hasError = false;
bool _usernameHasError = false;
bool _passwordHasError = false;
bool _keyboardVisible = false;
final KeyboardVisibilityController _keyboardVisibilityController =
KeyboardVisibilityController();
StreamSubscription<bool>? _keyboardVisibilitySubscription;
@override
void initState() {
super.initState();
_keyboardVisibilitySubscription = _keyboardVisibilityController.onChange
.listen(_handleKeyboardVisibilityChange);
}
@override
void dispose() {
_keyboardVisibilitySubscription?.pause();
_keyboardVisibilitySubscription?.cancel();
super.dispose();
}
void _handleKeyboardVisibilityChange(bool visible) {
setState(() {
_keyboardVisible = visible;
});
}
_handleFormSubmission() {
final rand = Random();
setState(() {
_hasError = rand.nextBool();
});
print('Sumbmitted');
}
_handleUsernameChange(String value) {
setState(() {
_usernameHasError = value.isEmpty;
});
}
_handlePasswordChange(String value) {
setState(() {
_passwordHasError = value.isEmpty;
});
}
@override
Widget build(BuildContext context) {
print('\nkeyboard is visible $_keyboardVisible');
return Scaffold(
body: Container(
padding: const EdgeInsets.all(10.0).add(const EdgeInsets.only(top: 30.0)),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
child: Column(children: [
Text('Large title',
style: Theme.of(context).textTheme.headline2),
_keyboardVisible ? Container() : Text(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, '
'sed do eiusmod tempor incididunt ut labore et dolore magna'
' aliqua. Ut enim ad minim veniam, quis nostrud exercitation '
'ullamco laboris nisi ut aliquip ex ea commodo consequat.',
style: Theme.of(context).textTheme.headline5),
]),
),
Container(
padding: const EdgeInsets.only(top: 10.0),
child: Column(children: [
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(bottom: 20.0),
child: Text('Additional information is placed here')),
TextFormField(
decoration: InputDecoration(
labelText: 'Username',
errorText: _usernameHasError ? 'Invalid username' : null),
onChanged: _handleUsernameChange,
),
TextFormField(
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
errorText: _passwordHasError ? 'Invalid password' : null),
onChanged: _handlePasswordChange,
),
_hasError
? Container(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error,
color: Theme.of(context).errorColor),
Text('An error occurred!',
style: TextStyle(
color: Theme.of(context).errorColor))
]))
: Container()
]),
),
Container(
padding: const EdgeInsets.only(top: 10.0),
child: Column(children: [
Container(
width: double.infinity,
child: ElevatedButton(
child: Text('Submit'),
onPressed: _handleFormSubmission,
)),
])),
]),
));
}
}
I am sure this could be done nicer, for example animating the widget out when it's being removed from widget tree. This is basic and it works.
As you can see the overflow does not happen anymore because there is now more vertical space:
Upvotes: 0
Reputation: 463
Since you want to make submit button visible on opening the keyboard you can do that like below. I made some changes in your code itself
return Scaffold(
body: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.all(10.0)
.add(const EdgeInsets.only(top: 30.0)),
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
child: Column(children: [
Text('Large title',
style: Theme.of(context).textTheme.headline2),
Text(
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, '
'sed do eiusmod tempor incididunt ut labore et dolore magna'
' aliqua. Ut enim ad minim veniam, quis nostrud exercitation '
'ullamco laboris nisi ut aliquip ex ea commodo consequat.',
style: Theme.of(context).textTheme.headline5),
]),
),
Container(
padding: const EdgeInsets.only(top: 10.0),
child: Column(children: [
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(bottom: 20.0),
child:
Text('Additional information is placed here')),
TextFormField(
decoration: InputDecoration(
labelText: 'Username',
errorText: _usernameHasError
? 'Invalid username'
: null),
onChanged: _handleUsernameChange,
),
TextFormField(
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
errorText: _passwordHasError
? 'Invalid password'
: null),
onChanged: _handlePasswordChange,
),
_hasError
? Container(
padding:
const EdgeInsets.symmetric(vertical: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error,
color: Theme.of(context).errorColor),
Text('An error occurred!',
style: TextStyle(
color:
Theme.of(context).errorColor))
]))
: Container()
]),
),
]),
),
),
),
Container(
padding: const EdgeInsets.only(top: 10.0),
child: Column(children: [
Container(
width: double.infinity,
child: ElevatedButton(
child: Text('Submit'),
onPressed: _handleFormSubmission,
)),
])),
],
));
Hope this will help you.
Upvotes: 1