4

I'm new in Compose and I'm having a problem with input text field on Wear OS. The problem is that I can't get soft keyboard to work as it usually does on Android. Also, when I tried to implement the same layout in XML - it worked. So, when I tap on input text field the keyboard pops up and then hides. When I tap again - keyboard pops up and stays open, but if I try to enter any text - nothing appears in the input field (on the keyboard itself), although entered text is passing down to input text field on UI.

Here is what I get in logs on emulator when I tap on input text field to open the keyboard:

2021-11-24 09:44:36.569 W/IInputConnectionWrapper: getTextBeforeCursor on inactive InputConnection
2021-11-24 09:44:36.571 W/IInputConnectionWrapper: getTextAfterCursor on inactive InputConnection
2021-11-24 09:44:36.649 W/RecordingIC: requestCursorUpdates is not supported

Here is what I get on real device:

2021-11-24 09:35:39.783 W/IInputConnectionWrapper: getExtractedText on inactive InputConnection
2021-11-24 09:35:39.872 W/IInputConnectionWrapper: beginBatchEdit on inactive InputConnection
2021-11-24 09:35:39.873 W/IInputConnectionWrapper: endBatchEdit on inactive InputConnection
2021-11-24 09:35:39.873 W/IInputConnectionWrapper: setComposingRegion on inactive InputConnection
2021-11-24 09:35:39.873 W/IInputConnectionWrapper: beginBatchEdit on inactive InputConnection
2021-11-24 09:35:39.873 W/IInputConnectionWrapper: endBatchEdit on inactive InputConnection
2021-11-24 09:35:39.873 W/IInputConnectionWrapper: beginBatchEdit on inactive InputConnection
2021-11-24 09:35:39.873 W/IInputConnectionWrapper: endBatchEdit on inactive InputConnection
2021-11-24 09:35:39.873 W/IInputConnectionWrapper: getExtractedText on inactive InputConnection
2021-11-24 09:35:39.882 W/IInputConnectionWrapper: beginBatchEdit on inactive InputConnection
2021-11-24 09:35:39.883 W/IInputConnectionWrapper: getTextBeforeCursor on inactive InputConnection
2021-11-24 09:35:39.884 W/IInputConnectionWrapper: getTextAfterCursor on inactive InputConnection
2021-11-24 09:35:39.888 W/IInputConnectionWrapper: getSelectedText on inactive InputConnection
2021-11-24 09:35:39.890 W/IInputConnectionWrapper: endBatchEdit on inactive InputConnection
2021-11-24 09:35:39.891 W/IInputConnectionWrapper: beginBatchEdit on inactive InputConnection
2021-11-24 09:35:39.891 W/IInputConnectionWrapper: endBatchEdit on inactive InputConnection
2021-11-24 09:35:39.891 W/IInputConnectionWrapper: beginBatchEdit on inactive InputConnection
2021-11-24 09:35:39.891 W/IInputConnectionWrapper: endBatchEdit on inactive InputConnection

And here is my 'composable':

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun ActivationScreen() {

    var key by remember { mutableStateOf("") }

    var isReady by remember {
        mutableStateOf(false)
    }

    Column(modifier = Modifier
        .padding(40.dp)
        .fillMaxSize()
    ) {
        val keyboardController = LocalSoftwareKeyboardController.current
        val focusRequester = FocusRequester()
        BasicTextField(
            value = key,
            onValueChange = {
                //isReady = it.length>11
                key = it
            },
            singleLine = true,
            keyboardOptions = KeyboardOptions.Default.copy(
                imeAction = ImeAction.Done
            ),
            keyboardActions = KeyboardActions(
                onDone = {
                    keyboardController?.hide()
                }
            ),
            modifier = Modifier
                .size(140.dp, 20.dp)
                .background(Color.White)
                .align(Alignment.CenterHorizontally)
                //.focusRequester(focusRequester)
                //.focusOrder(focusRequester)
        )

        Text(
            text = "ACTIVATION",
        )

        val status = if (isReady) "READY" else "NOT READY"
        Text(
            text = status,
        )
    }
}
Alex Bravo
  • 1,601
  • 2
  • 24
  • 40
Dmitry
  • 2,766
  • 1
  • 18
  • 17
  • 1
    Text Input on a wearable is not something that has been prioritised on Compose for WearOS, but is expected to eventually work. Other people have reported reproducible problems. Raise a bug here https://issuetracker.google.com/issues?q=componentid:1077552%20status:open – Yuri Schimke Nov 26 '21 at 11:33
  • I have similar issue. Do you resolve this problem? – AlexandraFilyakova Nov 09 '22 at 16:10

2 Answers2

9

You should avoid text input on Wear, but if you do really need it, the GBoard activity is the best way to activate.

See https://developer.android.com/reference/androidx/wear/input/RemoteInputIntentHelper.Companion#createActionRemoteInputIntent()

@Composable
fun TextInput() {
  val label = remember { mutableStateOf("Start")}
  val launcher =
    rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
      it.data?.let { data ->
        val results: Bundle = RemoteInput.getResultsFromIntent(data)
        val ipAddress: CharSequence? = results.getCharSequence("ip_address")
        label.value = ipAddress as String
      }
    }
  Column() {
    Spacer(modifier = Modifier.height(20.dp))
    Chip(
      label = { Text(label.value) },
      onClick = {}
    )
    Chip(
      label = { Text("Search with specific IP") },
      onClick = {
        val intent: Intent = RemoteInputIntentHelper.createActionRemoteInputIntent();
        val remoteInputs: List<RemoteInput> = listOf(
          RemoteInput.Builder("ip_address")
            .setLabel("Manual IP Entry")
            .wearableExtender {
              setEmojisAllowed(false)
              setInputActionType(EditorInfo.IME_ACTION_DONE)
            }.build()
        )

        RemoteInputIntentHelper.putRemoteInputsExtra(intent, remoteInputs)

        launcher.launch(intent)
      }
    )
  }
}

Note: As @TiagoPeresFrança mentioned in the comments:

In order to use wearableExtender, you must be on version 1.2.0+ of the wear-input dependency. As of today, version 1.2.0-alpha02 should be used. In your app gradle file, make sure you have as dependency implementation 'androidx.wear:wear-input:1.2.0-alpha02'.

NineToeNerd
  • 307
  • 5
  • 17
Yuri Schimke
  • 12,435
  • 3
  • 35
  • 69
  • Yuri, unfortunately, I have no choice - the app works in standalone mode and requires very basic text input. Your approach is exactly what I was looking for. Thank you! – Dmitry Dec 10 '21 at 21:31
  • @Dmitry Did you have to import or install some other libraries? I can't import RemoteInputIntentHelper – Francisco Hanna Jan 21 '22 at 22:53
  • @FranciscoHanna I did import androidx.wear.input.RemoteInputIntentHelper – Dmitry Jan 21 '22 at 23:54
  • 1
    In order to wearableExtender to work, you must be on version 1.2.0+ of the wear-input dependency. As of today, version 1.2.0-alpha02 should be used. In your app gradle file, make sure you have as dependency `implementation 'androidx.wear:wear-input:1.2.0-alpha02'`. – Tiago Peres França Feb 06 '22 at 13:31
  • 3
    Where did you get the wearableExtender ? – Jéwôm' Aug 24 '22 at 08:00
  • is it possible to disable the voice as well and open direct the keyboard? – muellerelias Aug 07 '23 at 13:27
0

Based on @Yuri Schimke's answer, here's a generic component implementation that accepts placeholder, value and onChange as attributes.

import android.app.RemoteInput
import android.content.Intent
import android.os.Bundle
import android.view.inputmethod.EditorInfo
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material.Chip
import androidx.wear.compose.material.Text
import androidx.wear.input.RemoteInputIntentHelper
import androidx.wear.input.wearableExtender

@Composable
fun TextInput(
  placeholder: String,
  value: String?,
  onChange: (value: String) -> Unit,
) {
  val launcher =
    rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
      it.data?.let { data ->
        val results: Bundle = RemoteInput.getResultsFromIntent(data)
        val newValue: CharSequence? = results.getCharSequence(placeholder)
        onChange(newValue as String)
      }
    }
  Column() {
    Chip(
      label = { Text(if (value == null || value.isEmpty()) placeholder else value) },
      onClick = {
        val intent: Intent = RemoteInputIntentHelper.createActionRemoteInputIntent();
        val remoteInputs: List<RemoteInput> = listOf(
          RemoteInput.Builder(placeholder)
            .setLabel(placeholder)
            .wearableExtender {
              setEmojisAllowed(false)
              setInputActionType(EditorInfo.IME_ACTION_DONE)
            }.build()
        )

        RemoteInputIntentHelper.putRemoteInputsExtra(intent, remoteInputs)

        launcher.launch(intent)
      }
    )
  }
}

Attention: this requires androidx.wear:wear-input:1.2.0+

Tiago Peres França
  • 3,056
  • 2
  • 21
  • 23