visit
It is assumed that you are familiar with adb
Running a kotlin script from a scratch file sometimes causes an error. A quick way around the problem is to create another scratch file.
Let's say we have an application that consists of two activities: LoginActivity and MainActivity. To go to MainActivity, you need to fill in the fields in LoginActivity and press Enter:
Code:
package com.leonidivankin.draftandroid.articles.autofill
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
title = "MainActivity"
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="//schemas.android.com/apk/res/android"
xmlns:app="//schemas.android.com/apk/res-auto"
xmlns:tools="//schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="24dp"
tools:context=".articles.autofill.LoginActivity">
<EditText
android:id="@+id/editTextEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="[email protected]"
app:layout_constraintBottom_toTopOf="@+id/editTextPhone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<EditText
android:id="@+id/editTextPhone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="+1 650 123 4567"
app:layout_constraintBottom_toTopOf="@+id/editTextPassword"
app:layout_constraintTop_toBottomOf="@+id/editTextEmail" />
<EditText
android:id="@+id/editTextPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="123456"
app:layout_constraintBottom_toTopOf="@+id/buttonEnter"
app:layout_constraintTop_toBottomOf="@+id/editTextPhone" />
<Button
android:id="@+id/buttonEnter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="enter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/editTextPassword" />
</androidx.constraintlayout.widget.ConstraintLayout>
package com.leonidivankin.draftandroid.articles.autofill
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.leonidivankin.draftandroid.databinding.ActivityLoginBinding
class LoginActivity : AppCompatActivity() {
private lateinit var binding: ActivityLoginBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root)
title = "LoginActivity"
binding.buttonEnter.setOnClickListener {
if (binding.editTextEmail.text.toString() == "[email protected]"
&& binding.editTextPhone.text.toString() == "+1 650 123 4567"
&& binding.editTextPassword.text.toString() == "123456"
)
startActivity(Intent(this, MainActivity::class.java))
}
}
}
To move to MainActivity, the fields must be filled in, and their values must meet certain requirements. For example, take the following values:
email: phone: +1 650 123 4567
As you can see, it takes about 1 minute from startup to MainActivity. This is still on the assumption that there were almost no errors in filling the data. Sometimes, finding complex bugs requires multiple rebuilds of the application and restarts on the device. This means constant filling of fields to get further into the app.
It turns out that with each restart we would lose about 1 minute extra. The ideal would be to jump directly into MainActivity, bypassing LoginActivity, but in large projects, this is most often impossible due to the need to get a token from the server.
Another possible solution to this problem would be to assign default values to these fields. However, this is not always possible, because the fields may be in an external SDK or in a webView. In this case, you won't be able to set default values.
As we continue I’ll show you how you can optimize the filling of the fields with kotlin-script.I will use the adb commands to fill the text fields. To fill one text field, we need:
To click on a text area, you must use the adb command:
adb shell input tap x y
There is an adb-command that knows how to determine the click coordinates:
adb shell getevent -l
Launch the Layout Inspector.
Select the necessary view.
Note that these coordinates are in dp. And the adb command needs coordinates in px. In my case it is x: 243=72, y: 3643=1092
adb shell input tap 72 1092
To find out the pixel density, you need to go to Device Manager:
In my case it is xxhdi. Here the ratio between dp and px is 3. The ratios are described in more detail .
Next, you must enter the desired text in the field. To do this, use the command:adb shell input text '[email protected]'
Enter it into the terminal and check that the necessary email is entered. Similar actions are carried out with other text fields: phone, password. Next, we need to click on the button, for this, there is a command:
adb shell input tap 72 1497
We found all the necessary adb commands to fill in all the text fields and click on the button. Here is the complete list:
adb shell input tap 72 1092
adb shell input text '[email protected]'
adb shell input tap 72 1227
adb shell input text '+1 650 123 4567'
adb shell input tap 72 1362
adb shell input text 123456
adb shell input tap 72 1497
Make a bash script.
Make a plugin for Android Studio.
Make Gradle task, etc.
But each of these methods has disadvantages. The bash script is difficult to maintain, the plugin for Android Studio needs to be maintained for new versions of the environment, gradle tasks take a long time to run due to dependencies with gradle and gradle wrapper.
So I use another way - kotlin-script (kts). The kotlin language is more familiar to the android developer, the script is independent of the project, android studio, and gradle.
Create a scratch file using Ctrl+Alt+Shift+Insert (Windows). In the window that appears, select Kotlin:
This will create a scratch.kts file. Notice the green arrow. This means that the file is executable and can be run. Uncheck Interactive mode so that the script won't restart every time you change the file.
There is a special method that allows you to execute an adb command from a kotlin script:
Runtime.getRuntime().exec()
fun run(){
run("adb shell am force-stop com.leonidivankin.draftandroid")
run("adb shell am start -n \"com.leonidivankin.draftandroid/com.leonidivankin.draftandroid.articles.autofill.LoginActivity\" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER")
Thread.sleep(2000)
run("adb shell input tap 72 1092")
run("adb shell input text '[email protected]'")
Thread.sleep(500)
run("adb shell input tap 72 1227")
run("adb shell input text '+1 650 123 4567'")
Thread.sleep(500)
run("adb shell input tap 72 1362")
run("adb shell input text 123456")
Thread.sleep(500)
run("adb shell input tap 72 1497")
}
fun run(command: String){
Runtime.getRuntime().exec(command)
}
This file consists of the adb commands we defined above with some additions.
Note that I inserted the lines first:
fun run(){
run("adb shell am force-stop com.leonidivankin.draftandroid")
run("adb shell am start -n \"com.leonidivankin.draftandroid/com.leonidivankin.draftandroid.articles.autofill.LoginActivity\" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER")
I also put the exec() command in a separate method to reduce the amount of code:
fun run(command: String){
Runtime.getRuntime().exec(command)
}
Note that some commands have Thread.sleep() between them. This is to wait for the application to start or the previous command to be entered. The point is that adb commands are executed asynchronously. And there is a probability that the focus will be shifted to the next text field without waiting for text input in the current one.
Total transition time to MainActivity from launching is not more than 5 seconds, which reduced the time by 20 times.