Building a Download Monitor With Android and Kotlin
Learn how to use Kotlin to build an Android app that shows a progress dialog with a download percentage.
Join the DZone community and get the full member experience.
Join For FreeIn this article, we will develop a simple app that will download a file and show a progress dialog with a percentage using Kotlin. Kotlin is getting popular nowadays and is used a lot in Android development. The objective of this article is to show how Kotlin can be used in Android development.
Environment Setup
Download Android Studio 3.0, if you have not installed it, from the Google site. This article uses Android Studio 3.0. The reason I am using Android Studio is that it supports Kotlin already so that you do not have to download it. For earlier versions of Android Studio, you will have to add Kotlin support manually.
Create an Android project with an Empty Activity. At this point, an Android project has been created but there is no support for Kotlin development. We are going to add it next.
Gradle Scripts
Go to your "build.gradle" (module:app) and add the following line in the beginning:
apply plugin: 'kotlin-android-extensions'
We have now added the Kotlin Android extension to the project.
Then, at the end of the script, in the dependencies section, add the following:
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
Sync with Gradle to build the project. Now you have set up the environment. Let us begin.
Download Tracker
Create a Kotlin class by right-clicking on the project and naming it DownloadActivity. It will create a file with the extension ".kt".
We are not going to spend much time on user interface design. Our user interface will have a single button called "Download" and this will trigger the download process. Please find the layout file, activity_main, below:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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" tools:context="com.downloader.MainActivity">
<Button android:id="@+id/downloadBtn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="132dp" android:layout_marginTop="80dp" android:text="Download" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
Open the Download.kt file and add the following lines:
class TestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main);
}
}
We are extending AppCompatActivty class in Kotlin. The ":" symbol shows that it is extending a class.
Next, we have overridden the onCreate lifecycle method by using the "override" and "fun" keywords. The third line is displaying the user interface with a single button in it.
Next, we are going to handle some UI operations. Upon clicking on the Download button, it will initiate a download process.
But how do we handle that? There are some synthetic functions in the Android Kotlin extension that will initialize the button id as a variable and can be used in the code, as shown below:
downloadBtn.setOnClickListener {
}
It is pretty cool, isn't? You do not have to call findByViewId(R.id.......). The Kotlin extension for Android will do it for you. In order to achieve this, we need to import this synthetic API in the code. Add the following line:
import kotlinx.android.synthetic.main.activity_main.*
Now we can add the code when the button is clicked. The above code snippet is trying to achieve what we talked about just now.
downloadBtn.setOnClickListener {
// we are going to call Download async task to begin our download
val task = DownloadTask(ProgressDialog(applicationContext));
task.execute("http://alex.smola.org/drafts/thebook.pdf");
}
You do not have to worry about null pointer exceptions. If the button element is not present, then the variable name is still available, but you may not able to call any operations on it. It will throw a compile time error saying "object reference not found" when we attempt to use any of the operations.
Download Task
This, as you guessed, is an AsyncTask that will download the file from the internet. At the moment, we have hardcoded the URL. The URL is a PDF file. You can change this value of your own choice. We are going to define the class below using an inner class of Kotlin. Let us do that now:
inner class DownloadTask (var dialog: ProgressDialog) : AsyncTask<String, Sting, String> {
}
And we need to override methods like onPreExecute, doInBackground, onPostExecute, onProgressUpdate, etc.
override fun onPreExecute() {
super.onPreExecute()
}
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
}
override fun onProgressUpdate(vararg values: String?) {
super.onProgressUpdate(*values)
}
Next, we need to display a progress dialog when we begin the download. When we defined an Async Task earlier, we passed an instance of ProgressDialog to its constructor. This type of constructor is called Primary Constructor because it does not contain any code. In the OnPreExecute method, we will set up this class, as shown below:
dialog.setTitle("Downloading file. Please wait..");
dialog.isIndeterminate = false;
dialog.max = 100;
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setCancelable(true);
dialog.show();
Please observe that we have not assigned the ProgressDialog instance to a variable, but still, it can be accessed from all of the methods of the class. This is because Kotlin treats it as a property implicitly and will be used for property initialization.
Next, we will code for downloading the file using a standard URLConnection API.
Download It Anyway
val url = URL(params[0]);
val connection: URLConnection = url.openConnection();
connection.connectTimeout = 30000;
connection.readTimeout = 30000;
connection.connect();
val length: Int = connection.contentLength;
val bis: BufferedInputStream = BufferedInputStream(url.openStream(), BUFFER_SIZE);
val data = ByteArray(BUFFER_SIZE);
var total: Long = 0;
var size: Int? = 0;
The above code opens a URL connection to the site and constructs a buffered input stream object with a default value of BUFFER_SIZE. The BUFFER_SIZE is a final static variable here with a value of 8192. We will see how we can define a final static variable in Kotlin.
In Kotlin, we can use "Companion Object" to define a final static variable, as shown below:
companion object {
// download buffer size
const val BUFFER_SIZE:Int = 8192;
}
We will now move on to the remaining part of the code.
while (true) {
size = bis.read(data) ?: break;
total += size.toLong();
val percent: Int = ((total * 100) / length).toInt();
publishProgress(percent.toString());
}
out.flush();
The second line in the above code snippet reads the data from the stream until it becomes null. Then, it will exit from the loop. The real beauty of Kotlin. The remaining part of the code is calculating the percent it progressed while downloading and updating the progress dialog.
override fun onProgressUpdate(vararg values: String) {
super.onProgressUpdate(*values)
// we always make sure that the the below operation will not throw null pointer exception
// other way is use null check like this // if(percent != null )
val percent = values[0].toInt();
dialog.progress = percent;
}
The above code will update the progress bar while downloading. If all goes well, you can see the progress.
Opinions expressed by DZone contributors are their own.
Comments