简体   繁体   中英

How to get String as a return value from "GlobalScope.launch" block

In this app I used HttpURLConnection to download Feed RSS "From XML link" then parse it and view into listview, but after I run the app I got empty listview

the code

 private fun downloadXML(urlPath: String? ): String {
        val xmlResult = StringBuilder()

        GlobalScope.launch(Dispatchers.IO) {
         kotlin.runCatching {
             try {
                 Log.d(TAG,"Dispatchers.IO Called")
                 val url = URL(urlPath)
                 val connection: HttpURLConnection = url.openConnection() as HttpURLConnection
                 val responseCode = connection.responseCode
                 Log.d(TAG, "DownloadXML: the response code is $responseCode")
                 connection.inputStream.buffered().reader().use { xmlResult.append(it.readText()) }
                 Log.e(TAG,"xmlResult: is ${xmlResult}")
             }catch (ex:Exception){
                 when(ex){
                     is MalformedURLException -> ex.message.toString()
                     is IOException -> ex.message.toString()
                     is SecurityException -> ex.message.toString()
                     else -> ex.message.toString()
                 }
             }
         }

        }
         Log.e(TAG,xmlResult.toString()) //====> THIS CODEDOES NOT EXECUTED
        return xmlResult.toString() // AND THIS
    }

and used it like this

 private var feedURL:String = "http://ax.itunes.apple.com/WebObjects/MZStoreServices.woa/ws/RSS/topfreeapplications/limit=10/xml"
    private var feedLimit = 10
    private var feedCashedURL = "INVALIDAED"
    private val STATE_URL = "feedURL"
    private val STATE_LIMIT = "feedLimit"
    private val parseApplication = ParseApplication()


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)


        downloadURL(feedURL.format(feedLimit))

        val feedAdapter = FeedAdapter(this,R.layout.list_record,parseApplication.application)
        binding.listView.adapter = feedAdapter

        Log.d(TAG,"onCreate called")

    }
    private fun downloadURL(feedURL: String) {

        if(feedURL != feedCashedURL){
            Log.d(TAG, "download URL starting AsyncTask")
            val rssFeed = downloadXML(feedURL)
            parseApplication.parse(rssFeed)
            feedCashedURL = feedURL
            Log.d(TAG, "downloadURL done")
        }else{
            Log.d(TAG, "downloadURL URL not changed")
        }


    }

it's look like those two lines doesn't executed because this I getting nothing in the result even though I got response code 200, and xmlResult printed but inside GlobalScope.launch block only, so how to getting this return result from the Coroutine block?

 Log.e(TAG,xmlResult.toString())
        return xmlResult.toString()

These two lines are being executed, but not in the order you think. GlobalScope.launch() starts asynchronous operation, it does not wait for it to finish, so return xmlResult.toString() is probably executed before xmlResult.append() .

I assume you tried to avoid blocking the main/UI thread. But note that your downloadURL() is still designed in the way that it needs to wait for resources to be downloaded before it can successfully return. And because this method is invoked from the UI thread, you can't avoid blocking this thread.

To solve this, you need to move the whole resource loading procedure into an asynchronous operation. That means: downloadXML() , downloadURL() and even feeding the adapter with data. Just remove your current GlobalScope.launch() , and inside onCreate() do something along lines:

      GlobalScope.launch(Dispatchers.IO) {

            val rssFeed = async { downloadXML(feedURL) }
            parseApplication.parse(rssFeed.await())

            withContext(Dispatchers.Main){
                val feedAdapter = FeedAdapter(this@MainActivity, R.layout.list_record, parseApplication.application)
                binding.listView.adapter = feedAdapter
            }

        }

I guess this may not be a fully working example, because you probably need to jump back to UI thread when feeding the adapter. Also, I believe using GlobalScope is not recommended, but I don't know Android very well, so I won't suggest a better solution.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM