How do I display data obtained within an asynchronous thread on my display while running an android development cortin?

Asked 2 years ago, Updated 2 years ago, 157 views

In your Android Cortin, how do you view data retrieved from asynchronous threads on your Android screen while running asynchronous threads?

As an example, I wrote the following code for downloading files from an Android device to an SMB server using jcifs-ng, a coll routine.
With this code, how do I display information (such as file name and size) on the Android screen when I connect to the SMB server?
I would like the Logcat output in the connectSmbDownload() function of the code below to be displayed in TextView (tvInfo2) on the Android screen.

import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import jcifs.CIFSContext
import jcifs.config.PropertyConfiguration
import jcifs.context.BaseContext
import jcifs.smb.NtlmPasswordAuthentication
import jcifs.smb.SmbFile
import kotlinx.coroutines.*
import java.io.File
import java.io.FileOutputStream
import java.util.*
import kotlin.coroutines.CoroutineContext


classMainActivity:AppCompatActivity(), CoroutineScope{
    // authentication information
    //////////////////////////////////////////////
    // Please replace these values to your data//
    val user="USER"
    val password = "PASS"
    val domain = "192.168.1.1"
    val smbroot="smb://"+domain+"/SMB/SERVER/FILE/"
    //////////////////////////////////////////////

    val TAG: String="MySMB"

    // coroutine preparation
    private val job = Job()
    override value coroutineContext
        get() = Dispatchers.Main+job

    // Coroutine Cancellation Settings at Exit
    override fun onDestroy() {
        job.cancel()
        super.onDestroy()
    }

    override fun onCreate (savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        valtvInfo1 = findViewById<View>(R.id.txtVwInfo1) as TextView
        valtvInfo2 = findViewById<View>(R.id.txtVwInfo2) as TextView
        valtvInfo3 = findViewById<View>(R.id.txtVwInfo3) as TextView

        launch {
            // output start-message in display
            tvInfo1.setText("Start!")

            // connect SMB server and download a file
            vallocalFile:File?=connectSmbDownload(user, password, domain, smroot)

            // output finish message in display
            if(localFile!=null){
                tvInfo3.setText(localFile.length().toString()+"Byte downloaded.")
            } else{
                tvInfo3.setText("no file downloaded.")
            }
        }
    }

    private suspend fun connectSmbDownload(user:String, password:String, domain:String, smroot:String): File?{
        return withContext (Dispatchers.IO) {
            // connect to SMB server
            val smb = connectSMB (user, password, domain, smbroot)
            Log.d(TAG, "Got SMB file:" + smb.path)

            // download a SMB file to android-device
            var downloadedFile:File?=null
            if(smb.isFile){
                downloadedFile=cpSmbFile2android(smb)
            } else{
                Log.d(TAG, smb.path+"is not a file.")
            }
            downloadedFile
        }
    }

    private suspend fun connectSMB(user:String, password:String, domain:String, smroot:String):SmbFile{
        return withContext (Dispatchers.IO) {
            val prop=Properties()
            prop.setProperty("jcifs.smb.client.minVersion", "SMB202")
            prop.setProperty("jcifs.smb.client.maxVersion", "SMB300")
            valbc = BaseContext (PropertyConfiguration(prop))
            valcreds=NtlmPasswordAuthentication(bc,domain,user,password)
            val auth —CIFSContext=bc. with Credentials (creds)
            SmbFile (smbroot, auth)
        }
    }

    /*
     * Copying a Remote SMB File to an Android Terminal
     *  The destination is the application cache area of the external storage of the Android terminal.
     *  smbFile source remote SMB file
     *  Return value destination file
     */
    private funcpSmbFile2android(smbFile:SmbFile):File?{
        if(!smbFile.isFile){
            return null
        }
        valfileName=smbFile.name
        valexterCacheFile=File(this.externalCacheDir!!.path+"/"+fileName)
        // copy remote smb-file to local
        val inStream=smbFile.openInputStream()
        valfileOutStream=FileOutputStream(exterCacheFile)
        valbuf = ByteArray (1024)
        varlen —Int=0
        while(true){
            len=inStream.read(buf)
            if(len<0)break
            fileOutStream.write(buf)
        }
        fileOutStream.flush();
        fileOutStream.close();
        inStream.close();
        return exterCacheFile
    }
}

android samba smb

2022-09-29 22:01

3 Answers

It's self-less.
As shown in the code below, we were able to pass the data in the asynchronous thread to the main thread by setting the location before and after the main thread as separate functions.
In this example, connecting to the SMB server and downloading files into one function was divided into SMB server connection function and file download function. After executing the SMB server connection function, the path of the server file is displayed on the Android screen.
I still don't know how to display the working status of asynchronous threads in the progress bar, so I would appreciate it if you could let me know.

import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import jcifs.CIFSContext
import jcifs.config.PropertyConfiguration
import jcifs.context.BaseContext
import jcifs.smb.NtlmPasswordAuthentication
import jcifs.smb.SmbFile
import kotlinx.coroutines.*
import java.io.File
import java.io.FileOutputStream
import java.util.*
import kotlin.coroutines.CoroutineContext


classMainActivity:AppCompatActivity(), CoroutineScope{
    // authentication information
    //////////////////////////////////////////////
    // Please replace these values to your data//
    val user="USER"
    val password = "PASS"
    val domain = "192.168.1.1"
    val smbroot="smb://"+domain+"/SMB/SERVER/FILE/"
    //////////////////////////////////////////////

    val TAG: String="MySMB"
    US>vartmpStr: String=""

    // coroutine preparation
    private val job = Job()
    override value coroutineContext
        get() = Dispatchers.Main+job

    // Coroutine Cancellation Settings at Exit
    override fun onDestroy() {
        job.cancel()
        super.onDestroy()
    }

    override fun onCreate (savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        valtvInfo1 = findViewById<View>(R.id.txtVwInfo1) as TextView
        valtvInfo2 = findViewById<View>(R.id.txtVwInfo2) as TextView
        valtvInfo3 = findViewById<View>(R.id.txtVwInfo3) as TextView

        launch {
            // output start-message in display
            tvInfo1.setText("Start!")

            withContext (Dispatchers.IO) {
                // connect SMB
                valuemb —SmbFile=connectSmb(user, password, domain, smbroot)
                tmpStr=java.lang.String.format("Got SMB file:%s", smb.path)
                tvInfo2.setText(tmpStr)

                // download a file from SMB to Android
                vallocalFile:File?=cpSmbFile2android(smb)
                if(null==localFile){
                    tmpStr=java.lang.String.format("Can't download:%s", smb.path)
                    tvInfo3.setText(tmpStr)
                } else{
                    tmpStr=java.lang.String.format("Downloaded%s.%sByte", smb.path, localFile.length())
                    tvInfo3.setText(tmpStr)
                }
            }
        }
    }

    private suspend fun connectSmb(user:String, password:String, domain:String, smroot:String):SmbFile{
        return withContext (Dispatchers.IO) {
            val prop=Properties()
            prop.setProperty("jcifs.smb.client.minVersion", "SMB202")
            prop.setProperty("jcifs.smb.client.maxVersion", "SMB300")
            valbc = BaseContext (PropertyConfiguration(prop))
            valcreds=NtlmPasswordAuthentication(bc,domain,user,password)
            val auth —CIFSContext=bc. with Credentials (creds)
            SmbFile (smbroot, auth)
        }
    }

    /*
     * Copying a Remote SMB File to an Android Terminal
     *  The destination is the application cache area of the external storage of the Android terminal.
     *  smbFile source remote SMB file
     *  Return value destination file
     */
    private funcpSmbFile2android(smbFile:SmbFile):File?{
        if(!smbFile.isFile){
            return null
        }
        valfileName=smbFile.name
        valexterCacheFile=File(this.externalCacheDir!!.path+"/"+fileName)
        // copy remote smb-file to local
        val inStream=smbFile.openInputStream()
        valfileOutStream=FileOutputStream(exterCacheFile)
        valbuf = ByteArray (1024)
        varlen —Int=0
        while(true){
            len=inStream.read(buf)
            if(len<0)break
            fileOutStream.write(buf)
        }
        fileOutStream.flush();
        fileOutStream.close();
        inStream.close();
        return exterCacheFile
    }
}


2022-09-29 22:01

It's self-less.
Using lifecycleScope, I was able to display the progress bar on the Android terminal screen while processing with asynchronous threads.
Here's the code:

import android.os.Bundle
import android.view.View
import android.widget.ProgressBar
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import jcifs.CIFSContext
import jcifs.config.PropertyConfiguration
import jcifs.context.BaseContext
import jcifs.smb.NtlmPasswordAuthentication
import jcifs.smb.SmbFile
import kotlinx.coroutines.*
import java.io.File
import java.io.FileOutputStream
import java.util.*
import kotlin.coroutines.CoroutineContext


classMainActivity:AppCompatActivity(), CoroutineScope{
    // authentication information
    //////////////////////////////////////////////
    // Please replace these values to your data//
    val user=""
    val password=""
    val domain = "192.168.1.1"
    val smbroot="smb://"+domain+"/SMB/SERVER/FILE/"
    //////////////////////////////////////////////

    val TAG: String="MySMB"
    US>vartmpStr: String=""

    // coroutine preparation
    private val job = Job()
    override value coroutineContext
        get() = Dispatchers.Main+job

    // Coroutine Cancellation Settings at Exit
    override fun onDestroy() {
        job.cancel()
        super.onDestroy()
    }

    override fun onCreate (savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        valtvInfo1 = findViewById<View>(R.id.txtVwInfo1) as TextView
        valtvInfo2 = findViewById<View>(R.id.txtVwInfo2) as TextView
        valtvInfo3 = findViewById<View>(R.id.txtVwInfo3) as TextView
        val prgrsBar = findViewById <View> (R.id.progressBar) as ProgressBar

        tvInfo1.setText("Start!")

        // Display programBar in the screen
        prgrsBar.visibility=ProgressBar.VISIBLE

        lifecycleScope.launch {
            valuemb —SmbFile=connectSmb(user, password, domain, smbroot)
            tmpStr=java.lang.String.format("Got SMB file:%s", smb.path)
            tvInfo2.setText(tmpStr)

            // download a file from SMB to Android
            vallocalFile —File=cpSmbFile2 android(smb)

            // Turn off programBar in the screen
            prgrsBar.visibility=ProgressBar.INVISIBLE

            tmpStr=java.lang.String.format("Downloaded%s.%sByte", smb.path, localFile.length())
            tvInfo3.setText(tmpStr)
        }
    }

    private suspend fun connectSmb(user:String, password:String, domain:String, smroot:String):SmbFile{
        return withContext (Dispatchers.IO) {
            val prop=Properties()
            prop.setProperty("jcifs.smb.client.minVersion", "SMB202")
            prop.setProperty("jcifs.smb.client.maxVersion", "SMB300")

            valsmbConnection=runCatching{
                valbc = BaseContext (PropertyConfiguration(prop))
                valcreds=NtlmPasswordAuthentication(bc,domain,user,password)
                val auth —CIFSContext=bc. with Credentials (creds)
                SmbFile (smbroot, auth)
            }
            smbConnection.getOrElse{
                US>throw Exception("connectSmb failed.")
            }

            /* This code is OK.
            return@withContext runCatching{
                valbc = BaseContext (PropertyConfiguration(prop))
                valcreds=NtlmPasswordAuthentication(bc,domain,user,password)
                val auth —CIFSContext=bc. with Credentials (creds)
                SmbFile (smbroot, auth)
            }
            .getOrElse{
                US>throw Exception("connectSmb failed.")
            }
            */
        }
    }

    /*
     * Copying a Remote SMB File to an Android Terminal
     *  The destination is the application cache area of the external storage of the Android terminal.
     *  smbFile source remote SMB file
     *  Return value destination file
     */
    private suspend funcpSmbFile2 android(smbFile:SmbFile):File{
        return withContext (Dispatchers.IO) {
            if(!smbFile.isFile){
                Null
            }
            valfileName=smbFile.name
            valexterCacheFile=File([email protected]!!.path+"/"+fileName)
            // copy remote smb-file to local
            return@withContext runCatching{
                val inStream=smbFile.openInputStream()
                valfileOutStream=FileOutputStream(exterCacheFile)
                valbuf = ByteArray (1024)
                varlen —Int=0
                while(true){
                    len=inStream.read(buf)
                    if(len<0)break
                    fileOutStream.write(buf)
                }
                fileOutStream.flush();
                fileOutStream.close();
                inStream.close();
                exterCacheFile
            }
            .getOrElse{
                US>throw Exception ("downloadSmb2 Android failed.")
            }
        }
    }
}


2022-09-29 22:01

It's self-less.
I was able to display progress bars that show progress in ViewModel, LiveData, and ViewModelScope, so I will post them below.

MainActivity.kt

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.ProgressBar
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import net.sytes.rokkosan.smbclient2.databinding.ActivityMainBinding

classMainActivity:AppCompatActivity(){

    //////////////////////////////////////////////
    // Please replace these values to your data//

    valDOMAIN: String="192.168.1.1"
    val SMBROOT: String="/SMB/SERVER/FILE/"
    val USER: String=""
    val PASSWORD: String=""

    //////////////////////////////////////////////

    lateinit var androidPath:String
    lateinit var smbViewModel:SmbViewModel

    override fun onCreate (savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val binding = ActivityMainBinding.inflate (layoutInflater)
        setContentView (binding.root)

        varisSmb —Boolean=false
        binding.progBarHori.max = 100

        // prepare for connecting SMB server
        varsmbUrl="smb://" + DOMAIN + SMBROOT
        if(smbUrl.takeLast(1)!="/"){
            smbUrl+="/"
            // The smbUrl string ends in a slash (/)
        }

        // get the cache directory in android device
        androidPath=externalCacheDir!!.absolutePath

        // getViewModel for SmbConnection
        valsmbViewModelFactory=SmbViewModelFactory(USER, PASSWORD, DOMAIN, smbUrl, androidPath)
        smbViewModel=ViewModelProvider(this,smbViewModelFactory).get(SmbViewModel::class.java)

        // Create observers which updates the UI.
        val pathObserver=Observer<String>{sPath->
            // Update the UI
            binding.tvSmbPath.text=sPath
        }
        valueObserver=Observer<Long>{sSize->
            // Update the UI
            binding.tvSmbSize.text=(sSize/1000L).toString()+"KB"
        }
        valdownloadedRatioObserver=Observer<Int>{dRatio->
            // Update the UI
            binding.progBarHori.progress=dRatio
        }
        valisConnectedObserver=Observer<Boolean>{isConnected->
            if(!isConnected){
                isSmb = false
                binding.tvInfo01.setText(R.string.failedConnectSmb)
            } else{
                isSmb = true

                /*
                 * get data in SMB server
                 *     Reference Page https://developer.android.com/topic/libraries/architecture/livedata?hl=ja
                 */
                // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
                //   Observe (Owner, ...) Owner should be this for Activity and viewLifecycleOwner for Fragment.
                smbViewModel.smbPath.observe (this, pathObserver)
                smbViewModel.smbSize.observe(this,sizeObserver)

                // Display programBar in the screen
                smbViewModel.downloadedRatio.observe (this, downloadedRatioObserver)
                binding.progBarHori.visibility=ProgressBar.VISIBLE
            }
        }

        // connect SMB server and download a file to android device
        binding.tvInfo01.setText(R.string.wait)
        smbViewModel.downloadSmbFile()

        // proceed recording to smbViewModel.isConnectedSmb
        smbViewModel.isConnectedSmb.observe(this, isConnectedObserver)
        if(!isSmb){
            return
        }
    }
}

SmbViewModel.kt

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import jcifs.config.PropertyConfiguration
import jcifs.context.BaseContext
import jcifs.smb.NtlmPasswordAuthenticator
import jcifs.smb.SmbFile
import kotlinx.coroutines.*
import java.io.File
import java.io.FileOutputStream
import java.util.*

class SmbViewModel (val user: String, val password: String, val domain: String, val smbUrl: String, val androidPath: String): ViewModel() {
    // Configure LiveData
    private val_smbPath:MutableLiveData<String>by lazy{
        MutableLiveData<String>()
    }
    valuembPath —LiveData<String>
        get() =_smbPath

    private val_smbSize:MutableLiveData<Long>by lazy{
        MutableLiveData<Long>()
    }
    valuembSize —LiveData<Long>
        get() =_smbSize

    private val_isConnectedSmb =MutableLiveData<Boolean>()
    valisConnectedSmb —LiveData<Boolean>
        get() =_isConnectedSmb

    private var_downloadedRatio:MutableLiveData<Int>=MutableLiveData<Int>().also{
            mutableLiveData ->mutableLiveData.value=-1
    }
    public val downloadedRatio —LiveData<Int>get()=_downloadedRatio

    // Connecting to SMB Server, Downloading Files
    fundownloadSmbFile(){
        viewModelScope.launch (Dispatchers.IO) {
            lateinit varsmb —SmbFile

            // connect to SMB server
            try{
                val prop=Properties()//java.util.Properties
                prop.setProperty("jcifs.smb.client.minVersion", "SMB202")
                prop.setProperty("jcifs.smb.client.maxVersion", "SMB300")
                valbaseCxt = BaseContext (PropertyConfiguration(prop))
                val auth=
                    baseCxt.with Credentials (NTlmPasswordAuthenticator(domain, user, password))
                smb=SmbFile(smbUrl,auth)
                if(!smb.exists()){
                    _isConnectedSmb.postValue(false)
                    return@launch
                }
            } catch(e:Exception){
                _isConnectedSmb.postValue(false)
                return@launch
            }

            _isConnectedSmb.postValue(true)

            // Set data from SMB server in LiveData
            _smbPath.postValue(smb.path)
            _smbSize.postValue(smb.length())

            // download a file from SMB server to android device
            valfileName=smb.name
            valfileSize = smb.length()
            valexterCacheFile=File(androidPath+"/"+fileName)
            val inStream=smb.openInputStream()
            valfileOutStream=FileOutputStream(exterCacheFile)
            valbuf = ByteArray (1024)
            varlen = 0
            var downloadedSize = 0L
            while(true){
                len=inStream.read(buf)
                if(len<0){
                    break
                }
                downloadedSize+=len.toLong()
                _downloadedRatio.postValue(downloadedSize*100L/fileSize).toInt())
                fileOutStream.write(buf)
            }
            fileOutStream.flush();
            fileOutStream.close();
            inStream.close()
        }
    }

    override fun onCleared() {
        super.onCleared()
        viewModelScope.cancel()
    }
}

SmbViewModelFactory.kt

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider

class SmbViewModelFactory(valuser:String, val password:String, val domain:String, val smbUrl:String, val androidPath:String):ViewModelProvider.Factory{
    override fun<T:ViewModel>create(modelClass:Class<T>):T{
        return SmbViewModel(user, password, domain, smbUrl, androidPath) as T
    }
}


2022-09-29 22:01

If you have any answers or tips


© 2024 OneMinuteCode. All rights reserved.