Fetching a URL in Android Kotlin asynchronously

Viewed 6827

So I'm trying to write a very simple Android app that fetches a response from a URL when a button gets pressed. The kotlin Android extensions have been advertised as a drop-in replacement for the boilerplate necessary in Java, so I tried my hand. Here's what I tried so far:

package com.example.susemihl.myapplication

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.widget.TextView
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.runBlocking
import java.net.URL

suspend fun fetch_url(url: String): String {
    return URL(url).readText()
}

fun fetch_async(url: String, view: TextView) = runBlocking {
    val result = async(CommonPool) { fetch_url(url) }
    view.setText(result.await())
}

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mainTextView.setText("Hello there.")
        mainButton.setOnClickListener {
            mainButton.setText("Check again.")
            fetch_async("https://random-app.appspot.com/", 
                        mainTextView)
        }

    }
}

This worked intermittently, but is now completely broken. There is no response to the button click. Print-debugging shows me that the thread gets executed, but seems to hang on the readText() call. Anything stupid I'm doing wrong here?

3 Answers

You have to switch to main thread in order to update the UI from a suspend function. I would do the networking logic in a ViewModel and expose the result as LiveData to your Activity:

class MainViewModel : ViewModel() {
  val urlLiveData = MutableLiveData<String>()

  fun fetchUrl(url: String): String {
    viewModelScope.launch {
      // Dispatchers.IO (main-safety block)
      withContext(Dispatchers.IO) {
        fetchAsync(url)
      }
    }
  }

  private suspend fun fetchAsync(url: String) {
    urlLiveData.postValue(URL(url).readText())  
  }

}

class MainActivity : AppCompatActivity() {
  private val mainViewModel by viewModels<MainViewModel>()

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    mainViewModel.urlLiveData.observe(
        viewLifecycleOwner,
        Observer { urlText ->
          mainTextView.setText(urlText)
        }
     )
   }

   mainViewModel.fetchUrl(""https://random-app.appspot.com/")
}

Here is a async sample that can be used with kotlin which is working perfect for me

val result = URL("<api call>").readText()

try {
        URL url = new URL("<api call>");
            urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection.setRequestMethod("GET");
        urlConnection.connect();
        InputStream inputStream = urlConnection.getInputStream();
            StringBuffer buffer = new StringBuffer();
        if (inputStream == null) {
            // Nothing to do.
            return null;
        }
        reader = new BufferedReader(new InputStreamReader(inputStream));
        String line;
        while ((line = reader.readLine()) != null) {
            buffer.append(line + "\n");
        }
        if (buffer.length() == 0) {
            return null;
        }
        result = buffer.toString();
    } catch (IOException e) {
        Log.e("Request", "Error ", e);
        return null;
    } finally{
        if (urlConnection != null) {
            urlConnection.disconnect();
        }
        if (reader != null) {
            try {
                reader.close();
            } catch (final IOException e) {
                Log.e("Request", "Error closing stream", e);
            }
        }
    }

@Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    task = new AsyncTask<Void, Void, String>() {
        @Override protected String doInBackground(Void... params) {
            return requestFromServer("<api call>");
        }

        @Override protected void onPostExecute(String s) {
            if (!isFinishing() && !isCancelled()) {
                Log.d("Request", s);
                Toast.makeText(ExampleActivity.this, "Request performed", Toast.LENGTH_LONG).show();
            }
        }
    };
}

@Override protected void onDestroy() {
    super.onDestroy();

    if (task != null) {
        task.cancel(true);
        task = null;
    }
}

Referenced from - antonioleiva

Related