\ Anyone who has encountered non-trivial bugs on android probably knows that, sometimes, it takes a lot of time and effort to fix them. Other times, the situation is even worse - it takes a lot of time to reproduce the bug. There are cases when there is nothing to do but restart the application under different conditions: with a network, without a network, without cache, with a bad network, with erroneous responses from the server, etc. \ The situation can be aggravated by the fact that constant restarts require a lot of long single-type actions, e.g. filling fields with data. In this article, I want to describe one possible solution to this problem. Let’s get to it # Observations and Assumptions * It is assumed that you are familiar with **adb** * This solution is suitable for filling fields of XML, compose, webView, and other visual types as well as other applications. * Running a **kotlin** script from a scratch file sometimes causes an error. [There is currently no clean solution.](https://github.com/fwcd/kotlin-language-server/issues/178) A quick way around the problem is to create another **scratch file**. \  # Selecting an example 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:** \ ```javascript 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" } } ``` \ ```javascript <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://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="login@gmail.com" 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> ``` \ ```javascript 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() == "login@gmail.com" && 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: [login@gmail.com](mailto:login@gmail.com) phone: +1 650 123 4567** \ [https://youtu.be/oX3NIZMYpyo](https://youtu.be/oX3NIZMYpyo) \ 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. # Autofill fields ## Finding the necessary adb commands I will use the **adb** commands to fill the text fields. To fill one text field, we need: \ 1. Click on the text area. 2. Fill it with text. \  To click on a text area, you must use the **adb** command: \ ```javascript adb shell input tap x y ``` \ where x and y are coordinates relative to the upper left corner of the device in pixels (px). They can be found in different ways: 1. There is an **adb-command** that knows how to determine the click coordinates: \ ```javascript adb shell getevent -l ``` \ But there are difficulties with the translation of coordinates. You can read more [here](https://proandroiddev.com/utilizing-adb-for-daily-tasks-b52a27715ee5). \ 2. On the real device, you can enable the developer's settings to display click coordinates or grid. 3. You can add a listener, which will give absolute coordinates at each click. 4. You can also find out through **Layout Inspector**. \ In this example, I am more comfortable using paragraph 4. 1. Launch the **Layout Inspector.** 2. Connect to the current process. 3. Select the necessary **view**. 4. Check its coordinates. It will depend on the size and type of the device. Most likely, your coordinates will be different. \ 5.  6. 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 7. The final command turns out to be: ```javascript adb shell input tap 72 1092 ``` 7. Go to the terminal and enter this command: 8.  9. The text input field is now active. 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 [here](https://stackoverflow.com/questions/28507609/image-resolution-for-mdpi-hdpi-xhdpi-and-xxhdpi). Next, you must enter the desired text in the field. To do this, use the command: ```javascript adb shell input text 'login@gmail.com' ``` \ 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: \ ```javascript adb shell input tap 72 1497 ``` \ I've already put the coordinates I need there. We found all the necessary **adb commands** to fill in all the text fields and click on the button. Here is the complete list: \ ```javascript adb shell input tap 72 1092 adb shell input text 'login@gmail.com' 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 ``` \ Now we need to build a script from these commands that will execute them instead of us. ## Creating a kotlin script You can build a script in different ways and in different languages. Here are some of the options: 1. Make a **bash script**. 2. Make a plugin for **Android Studio**. 3. 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: \ ```javascript Runtime.getRuntime().exec() ``` \ Insert all of our commands. My complete scratch file is as follows: \ ```javascript 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 'login@gmail.com'") 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: \ ```javascript 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") ``` \ These lines are needed when the application is running and you need to restart it. I also put the **exec()** command in a separate method to reduce the amount of code: \ ```javascript 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. \ [https://youtube.com/shorts/R484DB_RScc?feature=share](https://youtube.com/shorts/R484DB_RScc?feature=share) # Conclusion As Steve McConnell wrote in Perfect Code, programming is a complicated thing. It gets even more complicated because you have to look for complex and nontrivial bugs. So, from my point of view, the role of any optimizations is rather high. Despite the fact that within the framework of the whole project, they seem microscopic. Try to optimize the launch of your application by autocomplete fields. If you find bugs or know how else to improve the script, post in the comments.