Simon B
Simon B

Reputation: 587

Flutter - Textfield not showing up when keyboard appears on android

My problem is that when I focus on the textfield, the keyboard appears but the layout doesn't resize to keep it in the view. This problem is only with android and not iOS: Here a screenshot of what I mean:

Before and after on android:

Before After

Before and after on iOS:

enter image description here enter image description here

Here is my class :

class PlayerSelectionPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    int itemCount = Provider.of<PlayerProvider>(context).getPlayerList.length;
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.portraitUp,
    ]);
    return Scaffold(
      appBar: AppBar(
        title: Text(AppLocalizations.of(context).translate('player_selection_page_title')),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          HapticFeedback.mediumImpact();
          Navigator.push(context, MaterialPageRoute(builder: (context) => HomePage(), settings: RouteSettings(name: 'Home page')));
        },
        child: Icon(
          Icons.chevron_right,
          size: 30,
          color: Colors.white,
        ),
      ),
      body: itemCount > 0 ? ListView.builder(
          itemCount: itemCount,
          itemBuilder: (context, index) {
            return Column(
              children: [
                PlayerDismissible(index),
                Divider(
                  height: 0,
                )
              ],
            );
          }) : Container(
          padding: EdgeInsets.all(20),
          alignment: Alignment.topCenter,
          child: Text(AppLocalizations.of(context).translate('player_selection_page_empty_text'), textAlign: TextAlign.center, style: Theme.of(context).textTheme.subtitle2)
      ),
      bottomSheet: BottomPlayerBar(),
    );
  }
}

And here is my BottomPlayerBar() :

class BottomPlayerBar extends StatefulWidget{
  @override
  _BottomPlayerBarState createState() => _BottomPlayerBarState();
}

class _BottomPlayerBarState extends State<BottomPlayerBar> {
  String playerName;
  FocusNode myFocusNode;


  @override
  void initState() {
    super.initState();
    myFocusNode = FocusNode();
    SchedulerBinding.instance.addPostFrameCallback((_) {
      if (ModalRoute.of(context).isCurrent) {
        myFocusNode.requestFocus();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
        height: 80, color: Theme.of(context).primaryColor,
        padding: EdgeInsets.only(top: 20, bottom: 25, left: 20, right: 70),
        child: TextField(
          focusNode: myFocusNode,
          textCapitalization: TextCapitalization.words,
          onChanged: (val) => playerName = val.trim(),
          onSubmitted: (val) {
            if (playerName != null && playerName != '') {
              Provider.of<PlayerProvider>(context, listen: false).addPlayer(playerName);
              HapticFeedback.lightImpact();
              myFocusNode.requestFocus();
            } else {
              myFocusNode.unfocus();
            }
          },
          maxLength: 19,
          autocorrect: false,
          decoration: new InputDecoration(
              counterText: "",
              border: new OutlineInputBorder(
                borderSide: BorderSide.none,
                borderRadius: const BorderRadius.all(
                  const Radius.circular(30.0),
                ),
              ),
              filled: true,
              contentPadding: EdgeInsets.symmetric(vertical: 0, horizontal: 20),
              hintStyle: GoogleFonts.rubik(color: Colors.grey[500], fontWeight: FontWeight.bold),
              hintText: AppLocalizations.of(context).translate('player_selection_page_hint'),
              fillColor: Colors.white),
        )
    );
  }

  @override
  void dispose() {
    super.dispose();
    myFocusNode.dispose();
  }
}

Here is my android manifest:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="myApp">
    <application
        android:name="io.flutter.app.FlutterApplication"
        android:label="myApp !"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <meta-data
              android:name="io.flutter.embedding.android.LaunchTheme"
              android:resource="@style/LaunchTheme"
              />
            <meta-data
              android:name="io.flutter.embedding.android.SplashScreenDrawable"
              android:resource="@drawable/launch_background"
              />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
            <intent-filter>
                <action android:name="FLUTTER_NOTIFICATION_CLICK" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
</manifest>

Upvotes: 5

Views: 5495

Answers (9)

Iheb Briki
Iheb Briki

Reputation: 276

Following the app comments for me, I added a statefulBuilder, so the bottom sheet gets informed of the padding changes


  showModalBottomSheet(
      backgroundColor: Colors.transparent,
      barrierColor: Colors.black.withOpacity(0.2),
      elevation: 0.0,
      context: context,
      isScrollControlled: true,
      builder: (_) {
        return StatefulBuilder(builder: (context, StateSetter stateSetter) {
          return Padding(
            padding: MediaQuery.of(context).viewInsets,
            child: Column(
              children: [
                TextField(),
                TextField(),
                TextField(),
                TextField(),
              ],
            ),
          );
        });
      },
    );

Upvotes: 0

Javeed Ishaq
Javeed Ishaq

Reputation: 7105

android/app/src/main/res/values/styles.xml

For me changing below item property from true to false

<item name="android:windowFullscreen">false</item>

I had this issue, too. This line was added as a result of using the flutter_native_splash package.

Upvotes: 1

Sisir
Sisir

Reputation: 5408

Resizing the page when the soft keyboard appears is handled through the android:windowSoftInputMode activity parameter in AndroidManifest. See the official documnetation

If you have android:windowSoftInputMode="adjustResize" in your AndroidManifest.xml, the textfield will automatically reposition when the softkeyboard appears. The same behaviour will not happen if the parameter is not there in AndroidManifest.xml

Could you recheck this by modifying your AndroidManifest. That will also explain why it is not working only in Android but works in iOS (Because iOS handles this implicitly)

P.S: When modifying the AndoridManifest.xml, hot reload may not work. You will need an actual stop and start running the app.

Upvotes: 1

enfinity
enfinity

Reputation: 169

You can wrap your BottomPlayerBar widget with the Transform Widget as shown below.

Transform.translate(
    offset: Offset(0.0, -1 * MediaQuery.of(context).viewInsets.bottom),
    child: BottomPlayerBar(),
    );

Upvotes: 7

Alireza Abiri
Alireza Abiri

Reputation: 552

Use margin for your BottomPlayerBar widget:

margin: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),

Upvotes: 1

vmathieu
vmathieu

Reputation: 220

You can use a stack like this :

Widget build(BuildContext context) {
  return Scaffold(
    body: Stack(
      children: [
        Column(
          children: [
            Flexible(
              child: ListView.builder(itemBuilder: null),
            ),
            Container(
              width: double.infinity,
              height: 100,
              child: TextField(),
            )
          ],
        )
      ],
    ),
  );
}

Upvotes: 0

thusith.92
thusith.92

Reputation: 306

A simple solution might be to use the bottom padding for you BottomPlayerBar,

Instead of,

padding: EdgeInsets.only(top: 20, bottom: 25, left: 20, right: 70),

Try,

padding: EdgeInsets.only(top: 20, bottom: 25 + MediaQuery.of(context).padding.bottom, left: 20, right: 70),

Here we are just adding any padding added to the bottom of the scaffold from

MediaQuery.of(context).padding.bottom

Upvotes: 0

GensaGames
GensaGames

Reputation: 5788

So that's why I don't usually try to use Scaffold build in parameters. To save time on resolving platform dependent issues I would go with using simple Widgets to achieve result you want.

Widget build(BuildContext context) {
  return Scaffold(
    body: SingleChildScrollView(
      physics: NeverScrollableScrollPhysics(),
      child: Column(
            mainAxisSize: MainAxisSize.max,
            children: <Widget>[

              // YOU BODY VIEW 85 HEIGHT %
              ConstrainedBox(
                constraints: BoxConstraints(
                  minWidth: MediaQuery.of(context).size.width,
                  minHeight: MediaQuery.of(context).size.height * 0.85,
                ),
                child: MainWidget()
              ),

              // YOUR BOTTOM VIEW 15 %
              ConstrainedBox(
                constraints: BoxConstraints(
                  minWidth: MediaQuery.of(context).size.width,
                  minHeight: MediaQuery.of(context).size.height * 0.15,
                ),
                child: BottomPlayerBar()
              ),
            ],
        ),
    ),
  );
}

For your example above PlayerSelectionPage.

class PlayerSelectionPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    int itemCount = Provider.of<PlayerProvider>(context).getPlayerList.length;
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.portraitUp,
    ]);
    return Scaffold(
      appBar: AppBar(
        title: Text(AppLocalizations.of(context).translate('player_selection_page_title')),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          HapticFeedback.mediumImpact();
          Navigator.push(context, MaterialPageRoute(builder: (context) => HomePage(), settings: RouteSettings(name: 'Home page')));
        },
        child: Icon(
          Icons.chevron_right,
          size: 30,
          color: Colors.white,
        ),
      ),
      body: SingleChildScrollView(
        physics: NeverScrollableScrollPhysics(),
        child: Column(
              mainAxisSize: MainAxisSize.max,
              children: <Widget>[

                // NEW VIEW
                ConstrainedBox(
                constraints: BoxConstraints(
                  minWidth: MediaQuery.of(context).size.width,
                  minHeight: MediaQuery.of(context).size.height * 0.85,
                ),
              child: itemCount > 0 ? ListView.builder(
                  itemCount: itemCount,
                  itemBuilder: (context, index) {
                    return Column(
                      children: [
                        PlayerDismissible(index),
                        Divider(
                          height: 0,
                        )
                      ],
                    );
                  }) : Container(
                  padding: EdgeInsets.all(20),
                  alignment: Alignment.topCenter,
                  child: Text(AppLocalizations.of(context).translate('player_selection_page_empty_text'), textAlign: TextAlign.center, style: Theme.of(context).textTheme.subtitle2)
              ),
            ),

                // NEW VIEW
                ConstrainedBox(
                  constraints: BoxConstraints(
                    minWidth: MediaQuery.of(context).size.width,
                    minHeight: MediaQuery.of(context).size.height * 0.15,
                  ),
                  child: BottomPlayerBar()
                ),
              ],
          ),
      ),
      body: itemCount > 0 ? ListView.builder(
          itemCount: itemCount,
          itemBuilder: (context, index) {
            return Column(
              children: [
                PlayerDismissible(index),
                Divider(
                  height: 0,
                )
              ],
            );
          }) : Container(
          padding: EdgeInsets.all(20),
          alignment: Alignment.topCenter,
          child: Text(AppLocalizations.of(context).translate('player_selection_page_empty_text'), textAlign: TextAlign.center, style: Theme.of(context).textTheme.subtitle2)
      ),
    );
  }
}

Upvotes: 0

Piyush Kumar
Piyush Kumar

Reputation: 482

you can add isScrollControlled = true to showModalBottomSheet it'll allow the bottom sheet to take the full required height which gives more insurance that TextField is not covered by the keyboard.

showModalBottomSheet(
    shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(25.0))),
    backgroundColor: Colors.black,
    context: context,
    isScrollControlled: true,
    builder: (context) => Padding(
      padding: const EdgeInsets.symmetric(horizontal:18 ),
      child: BottomPlayerBar(),
          ),
    ));

Upvotes: 0

Related Questions