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
}
}
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
}
}
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.")
}
}
}
}
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
}
}
© 2024 OneMinuteCode. All rights reserved.