

androidのViewModelScopeを使ってサーバーのファイルにアクセスするアプリとして下記コードを書きました。  
サーバーのファイルにアクセスするメソッドとしてSmbViewModel.ktのconnectSmb1()を使えばうまく動きますが、connectSmb2()ではconnectSmb2()がfalseを返して失敗します。  
connectSmb1()では、サーバーファイルにアクセスするワーカースレッドでの作業終了後、メインスレッドにsleep()で5秒間の停止を入れています。  
connectSmb2()は、ワーカースレッドでの作業終了を待ってメインスレッドが動くようにするつもりでasync(Dispatchers.IO){}.await()を入れてみたものです。  
sleep()を使わずに動作するコードをお教えください。

MainActivity.kt

[code]

    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import androidx.lifecycle.Observer
    import androidx.lifecycle.ViewModelProvider
    import PACKAGE.smbclient2.databinding.ActivityMainBinding
    
    class MainActivity : AppCompatActivity() {
    
        //////////////////////////////////////////////
        val DOMAIN: String = "192.168.1.1"
        val SMBROOT: String = "/SMB/SERVER/FILE/"
        val USER: String = "FOO"
        val PASSWORD: String = "BAR"
        //////////////////////////////////////////////
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            val binding = ActivityMainBinding.inflate(layoutInflater)
            setContentView(binding.root)
    
            // prepare for connecting SMB server
            var smbUrl = "smb://" + DOMAIN + SMBROOT
    
            // get ViewModel for SmbConnection
            val smbViewModelFactory = SmbViewModelFactory(USER, PASSWORD, DOMAIN, smbUrl)
            val smbViewModel: SmbViewModel = ViewModelProvider(this, smbViewModelFactory).get(SmbViewModel::class.java)
    
            // connect SMB server
            if ( !smbViewModel.connectSmb2() ) {
                finishAndRemoveTask()
            }
    
            /*
             * get data in SMB server
             */
            // Create the observer which updates the UI.
            val pathObserver = Observer<String> { sPath ->
                binding.tvSmbPath.text = sPath
            }
            val sizeObserver = Observer<Long> { sSize ->
                binding.tvSmbSize.text = sSize.toString()
            }
    
            smbViewModel.smbPath.observe(this, pathObserver)
            smbViewModel.smbSize.observe(this, sizeObserver)
        }
    }
    
[/code]

  

SmbViewModel.kt

[code]

    import androidx.lifecycle.LiveData
    import androidx.lifecycle.MutableLiveData
    import androidx.lifecycle.ViewModel
    import androidx.lifecycle.viewModelScope
    import jcifs.CIFSContext
    import jcifs.config.PropertyConfiguration
    import jcifs.context.BaseContext
    import jcifs.smb.NtlmPasswordAuthentication
    import jcifs.smb.SmbFile
    import kotlinx.coroutines.*
    import java.util.*
    
    class SmbViewModel(val user: String, val password: String, val domain: String, val smbUrl: String): ViewModel() {
    
        // LiveDataを設定する
        private val _smbPath: MutableLiveData<String> by lazy {
            MutableLiveData<String>()
        }
        val smbPath: LiveData<String>
            get() = _smbPath
    
        private val _smbSize: MutableLiveData<Long> by lazy {
            MutableLiveData<Long>()
        }
        val smbSize: LiveData<Long>
            get() = _smbSize
    
        /*
         * SMBサーバーへの接続
         */
    
        // SMBサーバーへの接続例1
        fun connectSmb1(): Boolean {
            var retBool: Boolean = false
    
            viewModelScope.launch(Dispatchers.IO) {
                val prop = Properties()  // java.util.Properties
                prop.setProperty("jcifs.smb.client.minVersion", "SMB202")
                prop.setProperty("jcifs.smb.client.maxVersion", "SMB300")
                val bc = BaseContext(PropertyConfiguration(prop))
                val creds = NtlmPasswordAuthentication(bc, domain, user, password)
                val auth: CIFSContext = bc.withCredentials(creds)
                val smb = SmbFile(smbUrl, auth)
                if (smb.exists()) {
                    // LiveDataにSMBサーバーからのデータをセットする
                    _smbPath.postValue(smb.path)
                    _smbSize.postValue(smb.length())
                    retBool = true
                }
            }
            Thread.sleep(5000)
            return retBool
        }
    
    
        // SMBサーバーへの接続例2
        fun connectSmb2(): Boolean {
            var retBool: Boolean = false
    
            viewModelScope.launch{
                val retAsync = async(Dispatchers.IO) {
                    val prop = Properties()  // java.util.Properties
                    prop.setProperty("jcifs.smb.client.minVersion", "SMB202")
                    prop.setProperty("jcifs.smb.client.maxVersion", "SMB300")
                    val bc = BaseContext(PropertyConfiguration(prop))
                    val creds = NtlmPasswordAuthentication(bc, domain, user, password)
                    val auth: CIFSContext = bc.withCredentials(creds)
                    val smb = SmbFile(smbUrl, auth)
                    if (smb.exists()) {
                        // LiveDataにSMBサーバーからのデータをセットする
                        _smbPath.postValue(smb.path)
                        _smbSize.postValue(smb.length())
                        true
                    } else {
                        false
                    }
                }
                retBool = retAsync.await()
            }
            return retBool
        }
    
        override fun onCleared() {
            super.onCleared()
            viewModelScope.cancel()
        }
    }
    
[/code]

  

SmbViewModelFactory.kt

[code]

    import androidx.lifecycle.ViewModel
    import androidx.lifecycle.ViewModelProvider
    
    class SmbViewModelFactory(val user: String, val password: String, val domain: String, val smbUrl: String): ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return SmbViewModel(user, password, domain, smbUrl) as T
        }
    }
    
[/code]


CC BY-SA 4.0
2021-08-29T04:18:56.993
81095
2021-09-05T05:28:12.283
2021-08-29T04:22:49.617
3060
32105
question
android
android ViewModelScopeコルーチンでメインスレッドを停止する方法は？


自己レスです。  
メソッドの中でlaunch{}内のデータをlaunch外に渡すにはLiveDataを使うしかないことが
<https://stackoverflow.com/questions/67343540/how-to-send-the-result-of-
viewmodelscope-launch-as-the-return-value-of-a-funct> に書かれていました。  
次のコードで実現できました。  
ワーカースレッドのlaunch{}内で、LiveDataのisConnectedSmbに処理結果をセットし、メインスレッドでisConnectedSmbの値をobserveメソッドで取得しています。  
  
  
MainActivity.kt

[code]

    import androidx.appcompat.app.AppCompatActivity
    import android.os.Bundle
    import androidx.lifecycle.Observer
    import androidx.lifecycle.ViewModelProvider
    import net.sytes.rokkosan.smbclient2.databinding.ActivityMainBinding
    
    class MainActivity : AppCompatActivity() {
    
        //////////////////////////////////////////////
        val DOMAIN: String = "192.168.1.1"
        val SMBROOT: String = "/SMB/SERVER/FILE/"
        val USER: String = "FOO"
        val PASSWORD: String = "BAR"
        //////////////////////////////////////////////
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            val binding = ActivityMainBinding.inflate(layoutInflater)
            setContentView(binding.root)
    
            // prepare for connecting SMB server
            val smbUrl = "smb://" + DOMAIN + SMBROOT
    
            // get ViewModel for SmbConnection
            val smbViewModelFactory = SmbViewModelFactory(USER, PASSWORD, DOMAIN, smbUrl)
            val smbViewModel: SmbViewModel = ViewModelProvider(this, smbViewModelFactory).get(SmbViewModel::class.java)
    
            // connect SMB server
            smbViewModel.connectSmb()
            val isConnectedObserver = Observer<Boolean> { isConnected ->
                if (!isConnected) {
                    finishAndRemoveTask()
                }
            }
            smbViewModel.isConnectedSmb.observe(this,isConnectedObserver)
    
            /*
             * get data in SMB server
             */
            // Create the observer which updates the UI.
            val pathObserver = Observer<String> { sPath ->
                // Update the UI
                binding.tvSmbPath.text = sPath
            }
            val sizeObserver = Observer<Long> { sSize ->
                // Update the UI
                binding.tvSmbSize.text = sSize.toString()
            }
    
            smbViewModel.smbPath.observe(this, pathObserver)
            smbViewModel.smbSize.observe(this, sizeObserver)
        }
    }
    
[/code]

SmbViewModel.kt

[code]

    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.util.*
    
    class SmbViewModel(val user: String, val password: String, val domain: String, val smbUrl: String): ViewModel() {
    
        // LiveDataを設定する
        private val _smbPath: MutableLiveData<String> by lazy {
            MutableLiveData<String>()
        }
        val smbPath: LiveData<String>
            get() = _smbPath
    
        private val _smbSize: MutableLiveData<Long> by lazy {
            MutableLiveData<Long>()
        }
        val smbSize: LiveData<Long>
            get() = _smbSize
    
        private val _isConnectedSmb = MutableLiveData<Boolean>()
        val isConnectedSmb: LiveData<Boolean>
            get() = _isConnectedSmb
    
        // SMBサーバーへの接続
        fun connectSmb() {
            viewModelScope.launch(Dispatchers.IO) {
                val prop = Properties()  // java.util.Properties
                prop.setProperty("jcifs.smb.client.minVersion", "SMB202")
                prop.setProperty("jcifs.smb.client.maxVersion", "SMB300")
                val baseCxt = BaseContext(PropertyConfiguration(prop))
                val auth =
                    baseCxt.withCredentials(NtlmPasswordAuthenticator(domain, user, password))
                val smb = SmbFile(smbUrl, auth)
                if (smb.exists()) {
                    // LiveDataにSMBサーバーからのデータをセットする
                    _smbPath.postValue(smb.path)
                    _smbSize.postValue(smb.length())
                    _isConnectedSmb.postValue(true)
                } else {
                    _isConnectedSmb.postValue(false)
                }
            }
        }
    
        override fun onCleared() {
            super.onCleared()
            viewModelScope.cancel()
        }
    }
    
[/code]


CC BY-SA 4.0
2021-09-02T13:23:12.527
82187
2021-09-02T14:07:35.793
2021-09-02T14:07:35.793
32105
32105
81095
answer


自己レスです。  
メインスレッドを停止するのではなく、「ワーカースレッドでの処理結果が出次第、Activityで所要の処理を行う」という観点が必要でした。  
ワーカースレッドの結果をLiveDataでActivityに渡し、Observerでいろいろと処理すればよいです。  
下記参考ページを掲載します。  
<https://rasumus.hatenablog.com/entry/2021/09/03/221016>


CC BY-SA 4.0
2021-09-05T05:28:12.283
82247
2021-09-05T05:28:12.283
32105
81095
answer