Create Kotlin input control
OBJETIVOS
Create a Kotlin input control.
In this tutorial, we want to create a Kotlin input control that can get a phone number from a contact.
You can download a ready-to-use version of this input control from the Input Control gallery on github.
Manifest file
First, we have to create a manifest.json
file to register the input control in the app. The file contents:
{
"name": "phoneContact",
"type" : "text",
"target" : ["ios", "android"],
"capabilities": {
"android": [ "android.permission.READ_CONTACTS" ]
},
}
PhoneContact.kt
The PhoneContact.kt
file contains the logic to fetch the number of a contact on click. The file contents:
package ___PACKAGE___
import android.net.Uri
import android.provider.ContactsContract
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import com.qmobile.qmobiledatasync.utils.BaseKotlinInputControl
import com.qmobile.qmobiledatasync.utils.KotlinInputControl
import com.qmobile.qmobileui.activity.mainactivity.ActivityResultController
import com.qmobile.qmobileui.activity.mainactivity.MainActivity
import com.qmobile.qmobileui.ui.SnackbarHelper
import com.qmobile.qmobileui.utils.PermissionChecker
@KotlinInputControl
class PhoneContact(private val view: View) : BaseKotlinInputControl {
override val autocomplete: Boolean = false
override fun getIconName(): String {
return "call.xml"
}
private lateinit var outputCallback: (outputText: String) -> Unit
private val contactPhoneNumberCallback: (contactUri: Uri?) -> Unit = { contactUri ->
contactUri?.let {
(view.context as MainActivity?)?.apply {
contentResolver.query(contactUri, null, null, null, null)?.let { cursor ->
if (cursor.moveToFirst()) {
val contactIdIndex = cursor.getColumnIndex(ContactsContract.Contacts._ID)
val hasPhoneIndex = cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER)
val contactId = cursor.getString(contactIdIndex)
val hasNumber = cursor.getString(hasPhoneIndex)
if (Integer.valueOf(hasNumber) == 1) {
contentResolver.query(
ContactsContract. CommonDataKinds.Phone.CONTENT_URI,
null,
ContactsContract. CommonDataKinds.Phone.CONTACT_ID + " = " + contactId,
null,
null
)?.let { numbersCursor ->
while (numbersCursor.moveToNext()) {
val phoneNumberIndex =
numbersCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
val phoneNumber = numbersCursor.getString(phoneNumberIndex)
outputCallback(phoneNumber)
break
}
}
} else {
SnackbarHelper.show(this, "No phone number found in contact")
outputCallback("")
}
}
}
}
}
}
override fun process(inputValue: Any?, outputCallback: (output: Any) -> Unit) {
(view.context as PermissionChecker?)?.askPermission(
permission = android. Manifest.permission.READ_CONTACTS,
rationale = "Permission required to read contacts"
) { isGranted ->
if (isGranted) {
this.outputCallback = outputCallback
(view.context as ActivityResultController?)?.launch(
type = ActivityResultContracts. PickContact(),
input = null,
callback = contactPhoneNumberCallback
)
}
}
}
}
Note the following points:
- The
PhoneContact.kt
file must be located in theinputControl
folder at the following path: input-control-phoneContact/android/inputControlPhoneContact.kt - Our Kotlin input control must inherit from
BaseKotlinInputControl
interface. - On the first line, keep this
___PACKAGE___
line. It will be used and replaced at project generation. - The Kotlin input control main class is identified by the annotation
@KotlinInputControl
Once your app is generated, you will find your Kotlin input control in Your App / app / src / main / java / com / your_company / your_app / inputcontrol / YourCustomInputControl.kt
Auto fill
The autocomplete
variable sets if the Kotlin input control requires a click to call its process()
method or if it should process as soon as created.
Icon
If your Kotlin input control has an icon, override the method getIconName()
as follows :
override fun getIconName(): String {
return "call.xml"
}
Resource files
You are free to add any resource file to your Kotlin input control. Everything you put in a res folder under android folder will be copied to the generated app.
For example, your Kotlin .kt
input control can make use of a resource in res/drawable/your_resource.xml , therefore your will need to have a your resource.xml file in YourInputControl/android/res/drawable/your_resource.xml.
Permissions
You may need to add specific permissions to your app.
Add the following capabilities
block in your manifest.json
file:
{
...
"capabilities" : {
"android" : [ "android.permission.READ_CONTACTS" ]
}
}
There are registered permissions which you can simply define with the following:
{
...
"capabilities" : {
"contacts": true,
"location": true
}
}
contacts
setups android.permission.READ_CONTACTS
permission. location
setups android.permission.ACCESS_COARSE_LOCATION
and android.permission.ACCESS_FINE_LOCATION
permissions.
In order to ask user permission in your custom Kotlin input control, check out this code sample where we want the READ_CONTACTS
permission:
(context as PermissionChecker?)?.askPermission(
permission = android. Manifest.permission.READ_CONTACTS,
rationale = "Permission required to read contacts"
) { isGranted ->
if (isGranted) {
...
}
}
We offer access to PermissionChecker
that will delegate to the Activity
the permission request. The rationale
string will be displayed in case of the user denies the permission request.
ActivityResultController
We offer access to ActivityResultController
that will delegate any ActivityResultContracts
to MainActivity
. For example, to get a contact from your phone contacts, use ActivityResultContracts.PickContact()
.
(view.context as ActivityResultController?)?.launch(
type = ActivityResultContracts. PickContact(),
input = null,
callback = contactPhoneNumberCallback
)
ActivityResultContacts.PickContact()
is of ActivityResultContract<Void?, Uri?>
type. It means it takes no input, and returns the chosen contact Uri
. ActivityResultContracts.TakePicture()
is of ActivityResultContract<Uri, Boolean>
type. It means it takes an Uri
to store the image and returns if success or not.
The available contracts can be found here: https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContracts
.
We support the following types:
ActivityResultContracts.StartActivityForResult
of typeActivityResultContract<Intent, ActivityResult>
ActivityResultContracts.StartIntentSenderForResult
of typeActivityResultContract<IntentSenderRequest, ActivityResult>
ActivityResultContracts.RequestMultiplePermissions
of typeActivityResultContract<Array<String>, Map<String, Boolean>>
ActivityResultContracts.RequestPermission
of typeActivityResultContract<String, Boolean>
ActivityResultContracts.TakePicturePreview
of typeActivityResultContract<Void?, Bitmap?>
ActivityResultContracts.TakePicture
of typeActivityResultContract<Uri, Boolean>
ActivityResultContracts.CaptureVideo
of typeActivityResultContract<Uri, Boolean>
ActivityResultContracts.PickContact
of typeActivityResultContract<Void?, Uri?>
ActivityResultContracts.GetContent
of typeActivityResultContract<String, Uri?>
ActivityResultContracts.GetMultipleContents
of typeActivityResultContract<String, List<Uri>>
ActivityResultContracts.OpenDocument
of typeActivityResultContract<Array<String>, Uri?>
ActivityResultContracts.OpenMultipleDocuments
of typeActivityResultContract<Array<String>, List<Uri>>
ActivityResultContracts.OpenDocumentTree
of typeActivityResultContract<Uri?, Uri?>