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:
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.
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>
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:
Activity
or Fragment
BottomSheet
DialogFragment
This is the most common scenario where you would like the in-app keyboard to be integrated within the application itself.
To embed the Virtual Keyboard SDK as an in-app keyboard an the following steps must be carried out:
Create a class that inherits from the InAppKeyboardIntegration abstract class.
Initialise the InAppKeyboardSDK in the app’s Application class, providing the InAppKeyboardIntegration
and context.
android:windowSoftInputMode="stateAlwaysHidden"
to the Activity
that uses the InAppKeyboard
. It should be declared in the AndroidManifest.xml
.Add the FleksyKeyboardProvider in the app’s Activities and call it’s methods in the corresponding places:
onCreate()
onResume()
onBackPressed()
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:
onCreate(activity: Activity)
- initialises and creates the Keyboard and KeyboardView.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.onBackPressed(activity: Activity)
- handles the back button press to close the keyboard. It will return:
Go directly to the InAppKeyboard sample code that we have:
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.
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.
To embed the Virtual Keyboard SDK as an in-app keyboard for a BottomSheet
the following steps must be carried out:
Activity
or Fragment
already uses the FleksyKeyboardProvider
.
Activity
uses it, use the same instance.BottomSheet
calls in the corresponding places:
onCreate()
onResume()
onBackPressed()
onDismiss()
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.
Call the method keyboardProvider.onCreate()
in the BottomSheet’s onCreate
:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
keyboardProvider.onCreate(this)
}
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)
}
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()
}
}
}
Finally, when the BottomSheet
is dismissed, the method keyboardProvider.dismiss()
must be called:
override fun onDismiss(dialog: DialogInterface) {
keyboardProvider.dismiss()
super.onDismiss(dialog)
}
While less common, we do support the in-app keyboard on a DialogFragment
view.
The integration process is straightforward; follow these steps:
FleksyDialogFragment
class.getContentView
.InAppDialogListener
on the parent Activity
.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:
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()
}
}
⚠️ Important
It is recommended that the keyboard be closed by calling fleksyKeyboardProvider.hideKeyboard(this)
before opening a FleksyDialogFragment
or BottomSheet
.