44

Suppose I have some activity with a jetpack-compose content

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ScrollableColumn(
                modifier = Modifier
                    .fillMaxSize()
                    .border(4.dp, Color.Red)
            ) {
                val (text, setText) = remember { mutableStateOf("") }

                TextField(
                    value = text,
                    onValueChange = setText,
                    label = {},
                    modifier = Modifier
                        .fillMaxWidth()
                )

                for (i in 0..100) {
                    Text("Item #$i")
                }
            }
        }
    }

}

If I were to launch this activity and focus on the TextField a software keyboard would pop up.

The interface, however, would not react to it. ScrollableColumn's bottom border (.border(4.dp, Color.Red)) would not be visible, as well as 100th item (Text("Item #$i")).

In other words, software keyboard overlaps content.

How can I make jetpack compose respect visible area changes (due to software keyboard)?

Nycta
  • 1,087
  • 2
  • 9
  • 10

8 Answers8

32

You can use the standard android procedure, but I don't know if a Compose specific way exists.

If you set the SoftInputMode to SOFT_INPUT_ADJUST_RESIZE, the Layout will resize on keyboard change.

class YourActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        
        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
        
        setContent { /* Composable Content */ }
    }
}

otherwise, you could use the flags in the manifest. See here for more information: Move layouts up when soft keyboard is shown?

2jan222
  • 1,732
  • 2
  • 16
  • 29
  • 8
    It did the trick, `android:windowSoftInputMode="adjustResize"` (that which you hinted at) works as well. Thank you very much! – Nycta Sep 25 '20 at 02:44
  • Setting the flag in the manifest is the same as setting it in code just your preference I guess. – 2jan222 Sep 25 '20 at 10:10
  • 3
    I think this only set bottom inset when keyboard popup, but the content could still not fully visible after keyboard popup, unless `ScrollableColumn` would keep the focus item scrolling to visible position. – Yu-Hsuan Dec 19 '20 at 15:53
  • 4
    thanks but on keyboard ime next button click, the text field still not visible, ie the scrollable column not scrolled, any idea how to solve this ? – Hoby Jan 25 '21 at 19:20
  • @Hoby I think you have to scroll manually, you can calculate the position by measuring everything in the Column and then set the scroll state to the relative position of the focused element. – 2jan222 Jan 25 '21 at 20:25
  • @2jan222 first thanks a lot for your response, yeah this is what I did to solve this using 'scrollState.smoothScrollTo(i * viewSize)' but I was asking if there is any cleaner way – Hoby Jan 25 '21 at 21:03
  • @2jan222 do you have a code snippet to know how to perform this calculation ? – Oscar Ivan Sep 02 '21 at 21:49
  • @OscarIvan If you have a fixed height for the items you have to get the index of it multiply the height and then pass it to the remembered state for the list. I am not sure if you need to calculate in pixels or dp. If it is the first case you have to include a calculation including the current density. From the top of my head it's: Let``val d = LocalDensity.current`` then use ``val px = with(d) { dpValue.toPx() }`` – 2jan222 Sep 02 '21 at 21:55
  • @OscarIvan https://developer.android.com/reference/kotlin/androidx/compose/foundation/ScrollState#animateScrollTo(kotlin.Int,androidx.compose.animation.core.AnimationSpec) – 2jan222 Sep 02 '21 at 21:59
  • Adding: android:windowSoftInputMode="adjustResize" to your Activity element inside Android Manifest file works the best! – Stefan Feb 17 '22 at 18:46
  • 5
    SOFT_INPUT_ADJUST_RESIZE is deprecated, any updates on that? – barryalan2633 Apr 08 '22 at 23:28
  • I, personally, use the flag (should not be deprecated) in the manifest or Accompanist's inset library (answer below) – 2jan222 Apr 10 '22 at 12:15
  • Hello In my case, I have to handle both the scenario in my project: Example: Case 1: Need to resize the screen (AdjustResize) Case 2: No need to resize the screen (AdjustResize) If we give this in the manifest and activity how can I handle these two scenarios in my project: Note: Assume that both are different composes and screens. – Tippu Fisal Sheriff Oct 28 '22 at 05:38
  • Deprecated. See https://stackoverflow.com/a/74036164/4897831 – Adrián Prieto Jan 08 '23 at 18:10
  • it's not going to work for all layouts, your input field may appear behind keyboard... – user924 May 01 '23 at 17:44
26

Besides Compose, no external libraries are needed for this now. Set android:windowSoftInputMode=adjustResize on the activity in your manifest file

e.g.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <application>
        <activity
            android:name=".MyActivity"
            android:windowSoftInputMode="adjustResize"/>
    </application>

</manifest>

Then for the composable that you want the UI to react to, you can use Modifier.imePadding() which reacts to IME visibility

Box(Modifier.imePadding()) {
  // content
}
Andrew Orobator
  • 7,978
  • 3
  • 36
  • 36
  • Hello In my case, I have to handle both the scenario in my project: Example: Case 1: Need to resize the screen (AdjustResize) Case 2: No need to resize the screen (AdjustResize) If we give this in the manifest and activity how can I handle these two scenarios in my project: Note: Assume that both are different composes and screens. – Tippu Fisal Sheriff Oct 28 '22 at 05:39
  • I don't think "true" is a possible value for this field (which is misspelled) – Marty Miller Dec 26 '22 at 17:01
  • 1
    this is the correct answer , no need any more to 3rd library ... tested right now, imePadding do the job very well – issamux Jan 07 '23 at 05:25
  • it means that you may forget about adding `.imePadding()` for each screens where keyboard can be shown... because with `android:windowSoftInputMode="adjustResize"` the system doesn't put the content above the keyboard automatically – user924 May 01 '23 at 16:59
  • also what to do you if some screens may not have scrolling attribute (the content is adaptive)? but we set adjustResize for the whole activity, which means it effects all screens... – user924 May 01 '23 at 17:22
  • the problem that for some screens I need to have `android:windowSoftInputMode="adjustResize"` behaviour but for some not because it make hide your input field behind keyboard for some designs... I need `adjustResize` + `.imePadding()` only for screens like chat list (scrollable) with input field below the screen but for all others keep the default behaviour – user924 May 01 '23 at 17:40
22

You can use Accompanist's inset library https://google.github.io/accompanist/insets

first use ProvideWindowInsets at the root of your composable hierarchy most of the time below your app theme compose and set windowInsetsAnimationsEnabled true

ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
// content  }

The use navigationBarsWithImePadding() modifier on TextField

OutlinedTextField(
// other params,
modifier = Modifier.navigationBarsWithImePadding() )

Finaly make sure to call WindowCompat.setDecorFitsSystemWindows(window, false) from your activity(inside onCreate). If you want Software keyboard on/off to animate set your activity's windowSoftInputMode adjustResize in AndroidManifests

<activity
  android:name=".MyActivity"
  android:windowSoftInputMode="adjustResize">
mubarak adem
  • 251
  • 2
  • 4
3

I faced the same problem.

Use OnGlobalLayoutListener which will observe the actual IME rect size and will be triggered when the soft keyboard is fully visible.

Worked solution for me:

val bringIntoViewRequester = remember { BringIntoViewRequester() }
val scope = rememberCoroutineScope()
val view = LocalView.current
DisposableEffect(view) {
    val listener = ViewTreeObserver.OnGlobalLayoutListener {
        scope.launch { bringIntoViewRequester.bringIntoView() }
    }
    view.viewTreeObserver.addOnGlobalLayoutListener(listener)
    onDispose { view.viewTreeObserver.removeOnGlobalLayoutListener(listener) }
}
TextField(
    modifier = Modifier.bringIntoViewRequester(bringIntoViewRequester),
    ...
)

Origin here

2

I came up with idea of using accompanist insets library.

Someone could be interested because my approach does not modificate the contents of an application.

I link my post below:

(Compose UI) - Keyboard (IME) overlaps content of app

Drogheda
  • 209
  • 3
  • 16
2

In my case just adding android:windowSoftInputMode="adjustResize" to activity was enough to solve the problem.
It depends on how you build your UI. If yours screen's root is a vertically scrollable container or a Box, the keyboard resize might get managed automatically.

Waldmann
  • 1,563
  • 12
  • 25
2

Use the modifier imePadding(). This will allow the specific compositions to adjust themselves when the keyboard pops up. This does not require you to set any flag on the activity

apollosoftware.org
  • 12,161
  • 4
  • 48
  • 69
nightlaro
  • 29
  • 1
0

If you want your TextField scroll up when keyboard appears. The following it work for me. You need to add these

class YourActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
    
    window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
    
    setContent { /* Composable Content */ }
}

And add this to your app/build.gradle

implementation "com.google.accompanist:accompanist-insets-ui:0.24.7-alpha"
Nak Vanna
  • 19
  • 2