GameTextInput   Part of Android Game Development Kit.

Using the GameTextInput library is a simpler alternative to writing a full-screen Android app that uses the soft keyboard for text input.

GameTextInput provides a straightforward API to show or hide the soft keyboard, set or get the currently-edited text, and receive notifications when the text is changed. This is not meant for fully-fledged text editor apps, but still provides selection and composing region support for typical uses cases in games. Also, this library supports advanced input method editor (IME) features such as spell- checking, completions, and multi-key characters.

Set up your build

GameTextInput is distributed as an Android Archive (AAR). This AAR contains the Java classes and the C source code, which implements the native features of GameTextInput. You need to include these source files as part of your build process via Prefab, which exposes native libraries and source code to your CMake project or NDK build.

  1. If you aren't using GameActivity, follow the instructions on the Jetpack Android Games page to add the GameTextInput library dependency to your game's build.gradle file. The GameTextInput library is included as part of GameActivity, so you can skip this step if you're using GameActivity.

  2. Make sure gradle.properties contains the following lines:

    # Tell Android Studio we are using AndroidX.
    android.useAndroidX=true
    # Use Prefab 1.1.2 or higher, which contains a fix for "header only" libs.
    android.prefabVersion=1.1.2
    # Required only if you're using Android Studio 4.0 (4.1 is recommended).
    # android.enablePrefab=true
    
  3. If you aren't using GameActivity, in your project's CMakeLists.txt file, import the game-text-input package and add it to your target:

    find_package(game-text-input REQUIRED CONFIG)
    ...
    target_link_libraries(... game-text-input::game-text-input)
    
  4. In one of the .cpp files in your game, add the following line to include the GameTextInput implementation:

    #include <game-text-input/gametextinput.cpp>
    

    You may have already performed this step if you integrated GameActivity.

  5. Compile and run the app. If you have CMake errors, verify the AAR and the build.gradle files are properly set up. If the #include file is not found, verify your CMakeLists.txt configuration file.

    #include <game-text-input/gametextinput.h>
    

Integrate your build

You can integrate GameTextInput into your build either with or without GameActivity. Integrating with GameActivity is the recommended option to have your implementation in C or C++.

Integrate with GameActivity

Complete the following steps to integrate your build with GameActivity. Some of these steps involve the Endless Tunnel NDK sample. You may have different UI components and scenes in your game, but the concept remains the same.

  1. Ensure that your main Java activity extends GameActivity as described in Integrate Game Activity.

  2. In the following example from Endless Tunnel, create a button mNameEdit and use GameInputState to handle text input.

    class WelcomeScene : public UiScene {
    
    protected:
      UiWidget* mNameEdit;
      GameInputState mTextInputState;
      ...
    }
    
  3. Initialize GameInputState with default values. In the following example from Endless Tunnel, perform this in the WelcomeScene constructor.

    WelcomeScene::WelcomeScene() : mTextInputState{} {
      mTextInputState.text_UTF8 = INITIAL_NAME;
      mTextInputState.text_length = strlen(INITIAL_NAME);
      mTextInputState.selection.start = 0;
      mTextInputState.selection.end = mTextInputState.text_length;
      mTextInputState.composingRegion.start = -1;
      mTextInputState.composingRegion.end = -1;
    }
    
  4. Use GameActivity_setTextInputState and GameActivity_showSoftInput to show the keyboard and set the default edit text presented to the user. In the following example from Endless Tunnel, add this when the button is clicked.

    void WelcomeScene::OnButtonClicked(int id) {
      if (id == mNameEdit->GetId()) {
        auto activity = NativeEngine::GetInstance()->GetAndroidApp()->activity;
        // Note: the UI is resized when the IME is shown and OnCreateWidgets is
        // called again.
        sNameEdit = mTextInputState.text_UTF8;
        mNameEdit->SetText(sNameEdit.c_str());
        GameActivity_setTextInputState(activity, &mTextInputState);
        GameActivity_showSoftInput(activity, 0);
      }
    
  5. Use GameActivity to listen to a text event. We recommend using GameActivity with the android_native_app_glue library, which is bundled with GameActivity. You can also use it directly with the native callbacks. See the following generic examples for these two different methods:

    • If using with the android_native_app_glue library, check in your game loop whether a text input state change occurred:

      static void yourGameLoop(...) {
        ...
        if (mApp->textInputState) {
          // Handle the event here. See the next step with GameInputState_set
          // and GameActivity_getTextInputState.
        }
      }
      
    • If using with native callbacks, register the callback onTextInputEvent.

      // Use the callback that will handle the event:
      static void onTextInputEvent(GameActivity* activity,
                                   const GameInputState* state) {
        // Store or notify your game thread about the text input. You're not
        // forced to store the state here, because you can obtain it later.
        // See the next step.
      }
      
      // Register the callback:
      JNIEXPORT
      void GameActivity_onCreate(GameActivity* activity, void* savedState,
                                 size_t savedStateSize) {
        // ...
        activity->callbacks->onTextInputEvent = onTextInputEvent;
      }
      
  6. Use GameActivity_getTextInputState, GameInputState_set to read the text that was entered and store it in memory. You should display this to the user. In the following example, perform this in WelcomeScene::OnTextInputEvent.

    void WelcomeScene::OnTextInputEvent() {
      auto activity = NativeEngine::GetInstance()->GetAndroidApp()->activity;
      GameInputState_set(&mTextInputState,
        GameActivity_getTextInputState(activity));
      sNameEdit = std::string(mTextInputState.text_UTF8);
      mNameEdit->SetText(sNameEdit.c_str());
    }
    

Integrate without GameActivity

If you have integrated GameTextInput with GameActivity, skip to Utility Functions.

  1. From your main C thread, call GameInput_init with a JNIEnv pointer.

    static GameInput* gameInput = nullptr;
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_com_gameinput_testbed_MainActivity_onCreated(JNIEnv* env,
      jobject this) {
    {
        gameInput = GameInput_init(env);
        ...
    }
    
  2. Create a InputEnabledTextView Java class with access to InputConnection.

    public class InputEnabledTextView extends View implements Listener {
      public InputConnection mInputConnection;
    
      public void createInputConnection(int inputType) {
        EditorInfo editorInfo = new EditorInfo();
        editorInfo.inputType = inputType;
        editorInfo.actionId = IME_ACTION_NONE;
        mInputConnection = new InputConnection(this.getContext(), this,
          new Settings(editorInfo, true)
        ).setListener(this);
      }
    
      @Override
      public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
        if (outAttrs != null) {
          GameInput.copyEditorInfo(mInputConnection.getEditorInfo(), outAttrs);
        }
        return mInputConnection;
      }
    
      // Called when the IME input changes.
      @Override
      public void stateChanged(State newState, boolean dismissed) {
        onTextInputEventNative(newState);
      }
    
      private native void onTextInputEventNative(State softKeyboardEvent);
    }
    
  3. Add this new InputEnabledTextView class to your Java activity.

    public class MainActivity extends AppCompatActivity {
      ...
      InputEnabledTextView inputEnabledTextView;
      native void setInputConnectionNative(InputConnection c);
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
      ...
      inputEnabledTextView.createInputConnection(InputType.TYPE_CLASS_TEXT);
      setInputConnectionNative(inputEnabledTextView.mInputConnection);
    }
    
  4. In your C library, pass inputConnection into GameInput_setInputConnection. Pass a callback in GameInput_setEventCallback to be notified of events as C state structs.

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_gameinput_testbed_MainActivity_setInputConnectionNative(
      JNIEnv *env, jobject this, jobject inputConnection) {
      GameInput_setInputConnection(gameInput, inputConnection);
      GameInput_setEventCallback(gameInput, onEvent, env);
    }
    
  5. In your C library, call GameInput_processEvent to handle events when the state changes.

    extern "C"
    JNIEXPORT void JNICALL
    Java_com_gameinput_testbed_InputEnabledTextView_onTextInputEventNative(
      JNIEnv* env, jobject this, jobject soft_keyboard_event) {
      GameInput_processEvent(gameInput, soft_keyboard_event);
    }
    

Utility functions

The GameTextInput library includes utility functions that lets you convert between Java state objects and C state structs. Access functionality for showing and hiding the IME through the GameInput_showIme and GameInput_hideIme functions.