In-App Keyboard


There are use cases where the developers need a custom keyboard only for specific text fields within their app. In this case, having a system-wide keyboard is overkill and not a desired solution.

To mitigate this issue, Virtual Keyboard SDK supports integrating as an in-app keyboard without needing a system-wide keyboard. This way, the developers can use the SDK-backed keyboard only for specific text fields inside their app.

The in-app keyboard has two main elements:

  1. Edit text
  2. View where the keyboard is displayed

1. FleksyEditText

Use Fleksy’s edit text to make sure that the text entry is secure and no other third party keyboard is able to read it.

This editText inherits from the standard Android edittext with our InputConnection implementation.

1.1.Integration

  • Use the FleksyEditText from the KeyboardSDK.

Edit Text code

<co.thingthing.fleksy.core.keyboard.inapp.FleksyEditText
        android:id="@+id/fleksyEditText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        [...]
        />
        
    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/textInputLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        [...]
        >
        <co.thingthing.fleksy.core.keyboard.inapp.FleksyTextInputEditText
            android:id="@+id/textInputEditText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </com.google.android.material.textfield.TextInputLayout>

2. Keyboard Display

The in-app keyboard can be displayed as part of an Activity, Fragment, BottomSheet or as part of a DialogFragment.

To seamslessly integrate the in-app keyboard we categorize the integration process into three distinct types:

  1. Integrate it into a Activity or Fragment
  2. Integrate it into a BottomSheet
  3. Integrate it into a DialogFragment

2.1.Integration for an Activity or Fragment

This is the most common scenario where you would like the in-app keyboard to be integrated within the application itself.

1.1.1.Setup

To embed the Virtual Keyboard SDK as an in-app keyboard an the following steps must be carried out:

  1. Create a class that inherits from the InAppKeyboardIntegration abstract class.

    1. Provide a configuration via KeyboardConfiguration
  2. Initialise the InAppKeyboardSDK in the app’s Application class, providing the InAppKeyboardIntegration and context.

    1. Add android:windowSoftInputMode="stateAlwaysHidden" to the Activity that uses the InAppKeyboard. It should be declared in the AndroidManifest.xml.
  3. Add the FleksyKeyboardProvider in the app’s Activities and call it’s methods in the corresponding places:

    1. onCreate()
    2. onResume()
    3. onBackPressed()

1.1.2.Example

InAppKeyboardIntegration is the class that will be used to configure the SDK and receive events. It is an abstract class with just 1 compoulsory component, which is the application context.

abstract class InAppKeyboardIntegration(val context: Context) {
    open val getAppIcon: Int? = null
    open fun createConfiguration() = KeyboardConfiguration()
    lateinit var eventBus: EventBus
}

The integrator must create a class that inherits from InAppKeyboardIntegration and initialize the InAppKeyboardSDK with it. For example:

class Integration(context: Context): InAppKeyboardIntegration(context) {
       
       override fun createConfiguration(): KeyboardConfiguration{
          return KeyboardConfiguration()
       }
       
       init{
          eventBus.service.subscribe{
              TODO("Subscribe to any events from the EventBus")
          } 
       }
    }
class SDKApp : MultiDexApplication(){
    override fun onCreate() {
        super.onCreate()

        InAppKeyboardSDK.initialise(Integration(this))
    }
}

The final step is adding the FleksyKeyboardProvider to the desired activities via composition.

class SampleActivity: AppCompactActivity(){
    lateinit var keyboardProvider: FleksyKeyboardProvider
  
    override fun onCreate(savedInstanceState: Bundle?){
        super.onCreate()
        keyboardProvider = FleksyKeyboardProvider(this)
        keyboardProvider.onCreate()
        
        onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                if(!keyboardProvider.onBackPressed(this)){
                    finish()
                }
            }
        })
    }
 
    override fun onResume(savedInstanceState: Bundle?){
        super.onResume()
        keyboardProvider.onResume(this)
    }
}

The FleksyKeyboard provider does the following:

  1. onCreate(activity: Activity) - initialises and creates the Keyboard and KeyboardView.
  2. onResume(activity: Activity) - finds all EditTexts in the Activity’s child views and registers them to the CustomKeyboard. This essentially prevents them from opening the system keyboard and instead opens the custom keyboard.
  3. onBackPressed(activity: Activity) - handles the back button press to close the keyboard. It will return:
    • true - The event was already handled.
    • false - The event was not handled (keyboard was already closed).

1.1.3.Sample Code

Go directly to the InAppKeyboard sample code that we have:

2.2.BottomSheet Integration

In addition to integrating the keyboard within the main application, you might also want to integrate the in-app keyboard when a BottomSheet is displayed. In this scenario, follow these steps.

2.2.1.Requirements

1️⃣ The custom class for your BottomSheet (BottomSheetDialogFragment) must have an instance of the FleksyKeyboardProvider class.
2️⃣ If the host activity/fragment also uses InApp Keyboard it must be the same instance. Otherwise, you have to create a FleksyKeyboardProvider.

2.2.2.Setup

To embed the Virtual Keyboard SDK as an in-app keyboard for a BottomSheet the following steps must be carried out:

  1. Check if the Activity or Fragment already uses the FleksyKeyboardProvider.
    1. If the Activity uses it, use the same instance.
    2. If not, create an instance
  2. Add the FleksyKeyboardProvider in the BottomSheet calls in the corresponding places:
    1. onCreate()
    2. onResume()
    3. onBackPressed()
    4. onDismiss()

2.2.3.Example

2.2.3.1.Initial Configuration

Host Activity uses InApp Keyboard

The FleksyKeyboardProvider is the same instance for the Activity and for the Bottomsheet.

class BottomSheetFragment(private val keyboardProvider: FleksyKeyboardProvider) : BottomSheetDialogFragment()

In this case, when instancing the BottomSheet, you must provide the parent view’s FleksyKeyboardProvider. Also, the host’s InApp keyboard should be manually hidden before opening the BottomSheet. Here is how one would open the BottomSheet from the host view:

fun openBottomSheet() { 
    //Hide InApp keyboard if open 
    keyboardProvider.hideKeyboard(this@HostActivity)

    //Load custom BottomSheet 
    val bottomSheetDialog = BottomSheetFragment(keyboardProvider) 
    bottomSheetDialog.show(supportFragmentManager, "CUSTOM_BOTTOM_SHEET") 
} 

Host Activity does not use InApp Keyboard

class BottomSheetFragment() : BottomSheetDialogFragment(){ 
    private val keyboardProvider = FleksyKeyboardProvider()
}

After obtaining the keyboardProvider, let’s incorporate the provider in various calls.

2.2.3.2.OnCreate

Call the method keyboardProvider.onCreate() in the BottomSheet’s onCreate:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    keyboardProvider.onCreate(this)
}

2.2.3.3.OnResume

Register the view’s EditTexts in the BottomSheet’s onResume method:

override fun onResume() {
    super.onResume()
    keyboardProvider.registerEditText(this, binding.etTest1)
    keyboardProvider.registerEditText(this, binding.etTest2)
}

2.2.3.4.OnBackPressed

If you wish to handle the onBackPressed event to close the keyboard instead of the BottomSheet, you must do this when creating the Dialog:

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    return object : BottomSheetDialog(requireActivity(), theme) {
        override fun onBackPressed() {
            val handled = keyboardProvider.onBackPressed(this@BottomSheetFragment)
            if (!handled) super.onBackPressed()
        }
    }
}

2.2.3.5.OnDismiss

Finally, when the BottomSheet is dismissed, the method keyboardProvider.dismiss() must be called:

override fun onDismiss(dialog: DialogInterface) {
    keyboardProvider.dismiss()
    super.onDismiss(dialog)
}

2.3.Integration for a DialogFragment

While less common, we do support the in-app keyboard on a DialogFragment view.

2.3.1.Setup

The integration process is straightforward; follow these steps:

  1. Create your own DialogFragment that inherits from FleksyDialogFragment class.
  2. Implement the method getContentView.
  3. Implement the listener InAppDialogListener on the parent Activity.

2.3.2.FleksyDialogFragment Example

Take this as a reference on how to create a simple Dialog using FleksyDialogFragment:

class SimpleDialog : FleksyDialogFragment() {
        
    private lateinit var binding: SimpleDialogBinding
    
    /**
    * The dialog will use InAppDialogListener to communicate with the activity in order to
    * forward the messages to the FleksyKeyboardProvider.
    */
    private lateinit var inAppDialogListener: InAppDialogListener
    
    /**
      * Implementation of abstract method. Here we can provide the custom view for the dialog.
      * This method replaces onCreateView which must NOT be overwritten.
      */                   
    override fun getContentView(inflater: LayoutInflater, container: ViewGroup?): View {
        binding = SimpleDialogBinding.inflate(inflater, container, false)
        return binding.root
    }
    
    /**
      * Gets a the instance of the parent Activity which implements InAppDialogListener.
      */
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        inAppDialogListener = requireActivity() as InAppDialogListener
        inAppDialogListener.onCreate(this)
    }

    /**
      * Register every editText where you wish to use the InApp keyboard.
      */
    override fun onResume() {
        super.onResume()
        inAppDialogListener.registerEditText(this, binding.editText)
    }

    /**
      * In dialogs, we must call FleksyKeyboardProvider.dismiss() in order to return control
      * of the keyboard to the parent Activity or Fragment.
      */
    override fun onDismiss(dialog: DialogInterface) {
        super.onDismiss(dialog)
        inAppDialogListener.dismiss()
    }
}

Notes:

  • It is imperative that the method onCreateView is not overridden in the FleksyDialogFragment.

On the Activity code we have to implement the InAppDialogListener:

class MainActivity : AppCompatActivity(), InAppFragmentListener,
    InAppDialogListener {

    /**
    * FleksyKeyboardProvider
    */
    private var kProvider: FleksyKeyboardProvider? = null
    
    /**
    * Since we cannot control when the system recreates the view, we must ensure
    * the FleksyKeyboardProvider is started before using it.
    */
    private fun getSafeKeyboardProvider(): FleksyKeyboardProvider {
        if (kProvider == null) {
            kProvider = FleksyKeyboardProvider()
            kProvider!!.onCreate(this)
        }

        return kProvider!!
    }
    
    override fun onCreate() {
        getSafeKeyboardProvider().onCreate(this)
    }

    /**
    * InAppDialogListener methods
    */
    override fun onCreate(dialog: DialogFragment) {
        getSafeKeyboardProvider().onCreate(dialog)
    }

    override fun registerEditText(
        dialog: DialogFragment,
        editText: EditText,
        keyboardConfiguration: KeyboardConfiguration?
    ) {
        getSafeKeyboardProvider().registerEditText(dialog, editText, keyboardConfiguration)
    }

    override fun onBackPressed(dialog: DialogFragment): Boolean {
        return getSafeKeyboardProvider().onBackPressed(dialog)
    }

    override fun hideKeyboard(dialog: DialogFragment) {
        getSafeKeyboardProvider().hideKeyboard(dialog)
    }

    override fun dismiss() {
        getSafeKeyboardProvider().dismiss()
    }
}

2.3.Additional Notes

⚠️ Important

It is recommended that the keyboard be closed by calling fleksyKeyboardProvider.hideKeyboard(this) before opening a FleksyDialogFragment or BottomSheet.


If something needs to be added or if you find an error in our documentation, please let us know either on our GitHub or Discord.

Last updated on March 22, 2024