Android 协程块 UI 点击监听器
Android Coroutines block UI click listener
我使用 MVVM 作为架构,也是存储库模式。我有一个 Web 服务,还有一个房间数据库。使用协同程序阻止我单击的任何按钮。
有一个 list/detail 分别用片段和 activity 实现。
我可以弄清楚我实现协程和 Viewmodel 的方式出了什么问题。
class BuySharedViewModel(application: Application) : AndroidViewModel(application) {
private val repository: BuyRepository
var allBuys: LiveData<List<Buy>>
init {
val buyDao = KunukRoomDatabase.getDatabase(application, viewModelScope).buyDao()
val buyRemote = BuyRemote()
repository = BuyRepository.getInstance(buyDao , buyRemote)
//Use async because it return a result
viewModelScope.launch { getAllBuys() }
allBuys = buyDao.loadAllBuys()
}
private suspend fun getAllBuys() {
repository.getBuys()
}
}
这是存储库,它从 Web 服务获取数据并将其添加到房间数据库,而 ViewModel 从房间数据库获取数据。
class BuyRepository (private val buyDao: BuyDao, private val buyRemote: BuyRemote) {
private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.Default + job)
companion object {
//For singleton instantiation
@Volatile private var instance: BuyRepository? = null
fun getInstance(buyDao: BuyDao, buyRemote: BuyRemote) =
instance ?: synchronized(this) {
instance ?: BuyRepository(buyDao, buyRemote)
.also { instance = it}
}
}
suspend fun getBuys(){
refresh()
}
private suspend fun refresh(){
try {
val list = scope.async { buyRemote.loadBuys() }
list.await().forEach { buy -> insert(buy) }
} catch (e: Throwable) {}
}
@WorkerThread
private fun insert(buy: Buy) {
buyDao.insertBuy(buy)
}
}
片段工作,显示数据,当我点击该片段(recyclerView)中的一个项目时,activity 显示详细数据。但是 none 对 activity 的点击有效,就像它没有检测到点击一样。我猜它与协程有关,因为当我注释掉 BuySharedViewModel 中的代码 viewmodelScope.launch { getAllBuys()}
时,它起作用了,因为它从房间数据库中加载了先前调用的数据,并且点击有效。
详细视图中的代码如下:
class BuyDetailActivity : AppCompatActivity() {
private lateinit var sharedViewModel: BuySharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lateinit var buy: Buy
sharedViewModel = ViewModelProviders.of(this).get(BuySharedViewModel::class.java)
val position = intent.getIntExtra("position", 0)
sharedViewModel.allBuys.observe(this, Observer<List<Buy>> { buys ->
buy = buys[position]
val binding: com.example.drake.kunuk.databinding.ActivityBuyDetailBinding =
DataBindingUtil.setContentView(this, com.example.drake.kunuk.R.layout.activity_buy_detail)
binding.buy = buy
val agentNumber = buy.agentNumber?:"+50937438713"
bnvContactAgent.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
com.example.drake.kunuk.R.id.action_call -> {
val callNumberUri = Uri.parse("tel:$agentNumber")
val callIntent = Intent(Intent.ACTION_DIAL, callNumberUri)
startActivity(callIntent)
}
com.example.drake.kunuk.R.id.action_sms -> {
val smsNumberUri = Uri.parse("sms:$agentNumber")
val smsIntent = Intent(Intent.ACTION_SENDTO, smsNumberUri)
startActivity(smsIntent)
}
com.example.drake.kunuk.R.id.action_email -> {
val uriText = "mailto:drakecolin@gmail.com" +
"?subject=" + Uri.encode("I'm interested in $agentNumber") +
"&body=" + Uri.encode("Hello, ")
val uri = Uri.parse(uriText)
val sendIntent = Intent(Intent.ACTION_SENDTO)
sendIntent.data = uri
startActivity(Intent.createChooser(sendIntent, "Send email"))
}
}
false
}
这是我的片段代码:
class BuyFragment : Fragment() {
companion object {
fun newInstance() = BuyFragment()
}
private lateinit var viewModel: BuySharedViewModel
private val buyList = ArrayList<Buy>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Get a new or existing ViewModel from the ViewModelProvider.
viewModel = ViewModelProviders.of(this).get(BuySharedViewModel::class.java)
// Add an observer on the LiveData returned by loadAllBuys.
// The onChanged() method fires when the observed data changes and the activity is
// in the foreground.
viewModel.allBuys.observe(this, Observer<List<Buy>> { buys ->
// Update the cached copy of the words in the adapter.
buys?.let { (rvBuy.adapter as BuyAdapter).setBuys(it) }
progressBar.visibility = View.GONE
})
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.buy_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
rvBuy.layoutManager = LinearLayoutManager(context)
rvBuy.adapter = BuyAdapter(activity!!.applicationContext,
R.layout.buy_card, buyList)
progressBar.visibility = View.VISIBLE
}
}
这是 BuyDao 的代码:
@Dao
接口 BuyDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
有趣的插入购买(可变参数购买:购买)
@Update
fun updateBuy(vararg buys: Buy)
@Delete
fun deleteBuys(vararg buys: Buy)
@Query("SELECT * FROM buys")
fun loadAllBuys(): LiveData<List<Buy>>
@Query("DELETE FROM buys")
suspend fun deleteAll()
}
viewModelScope
默认使用 Dispatchers.Main
并且它会阻止您的 UI.
试试这个:
viewmodelScope.launch(Dispatchers.IO) { getAllBuys()}
编辑:
问题是您在更新实时数据时在 BottomNavigation 上设置了监听器,这导致了这个奇怪的问题。
将您的 BuyDetailActivity
代码替换为:
class BuyDetailActivity : AppCompatActivity() {
private lateinit var sharedViewModel: BuySharedViewModel
private var agentNumber = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityBuyDetailBinding =
DataBindingUtil.setContentView(this, R.layout.activity_buy_detail)
binding.buy = Buy()
lateinit var buy: Buy
sharedViewModel = ViewModelProviders.of(this).get(BuySharedViewModel::class.java)
val position = intent.getIntExtra("position", 0)
sharedViewModel.allBuys.observe(this, Observer<List<Buy>> { buys ->
buy = buys[position]
binding.buy = buy
binding.executePendingBindings()
agentNumber = buy.agentNumber
// set animation duration via code, but preferable in your layout files by using the animation_duration attribute
expandableTextView.setAnimationDuration(750L)
// set interpolators for both expanding and collapsing animations
expandableTextView.setInterpolator(OvershootInterpolator())
// or set them separately.
expandableTextView.expandInterpolator = OvershootInterpolator()
expandableTextView.collapseInterpolator = OvershootInterpolator()
// toggle the ExpandableTextView
buttonToggle.setOnClickListener {
buttonToggle.setText(if (expandableTextView.isExpanded) com.example.drake.kunuk.R.string.more else com.example.drake.kunuk.R.string.less)
expandableTextView.toggle()
}
// but, you can also do the checks yourself
buttonToggle.setOnClickListener {
if (expandableTextView.isExpanded) {
expandableTextView.collapse()
buttonToggle.setText(com.example.drake.kunuk.R.string.more)
} else {
expandableTextView.expand()
buttonToggle.setText(com.example.drake.kunuk.R.string.less)
}
}
//Open photoView activity when clicked
ivHouseDetail.setOnClickListener {
applicationContext
.startActivity(
Intent(
applicationContext,
ViewPagerActivity::class.java
)
.putExtra("imageList", buy.propertyImage)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
}
})
findViewById<BottomNavigationView>(R.id.bnvContactAgent)?.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.action_call -> {
Log.e("BIRJU", "Action call")
val callNumberUri = Uri.parse("tel:$agentNumber")
val callIntent = Intent(Intent.ACTION_DIAL, callNumberUri)
startActivity(callIntent)
}
R.id.action_sms -> {
Log.e("BIRJU", "Action SMS")
val smsNumberUri = Uri.parse("sms:$agentNumber")
val smsIntent = Intent(Intent.ACTION_SENDTO, smsNumberUri)
startActivity(smsIntent)
}
R.id.action_email -> {
Log.e("BIRJU", "Action Email")
val uriText = "mailto:drakecolin@gmail.com" +
"?subject=" + Uri.encode("I'm interested in $agentNumber") +
"&body=" + Uri.encode("Hello, ")
val uri = Uri.parse(uriText)
val sendIntent = Intent(Intent.ACTION_SENDTO)
sendIntent.data = uri
startActivity(Intent.createChooser(sendIntent, "Send email"))
}
}
false
}
}
}
我使用 MVVM 作为架构,也是存储库模式。我有一个 Web 服务,还有一个房间数据库。使用协同程序阻止我单击的任何按钮。
有一个 list/detail 分别用片段和 activity 实现。
我可以弄清楚我实现协程和 Viewmodel 的方式出了什么问题。
class BuySharedViewModel(application: Application) : AndroidViewModel(application) {
private val repository: BuyRepository
var allBuys: LiveData<List<Buy>>
init {
val buyDao = KunukRoomDatabase.getDatabase(application, viewModelScope).buyDao()
val buyRemote = BuyRemote()
repository = BuyRepository.getInstance(buyDao , buyRemote)
//Use async because it return a result
viewModelScope.launch { getAllBuys() }
allBuys = buyDao.loadAllBuys()
}
private suspend fun getAllBuys() {
repository.getBuys()
}
}
这是存储库,它从 Web 服务获取数据并将其添加到房间数据库,而 ViewModel 从房间数据库获取数据。
class BuyRepository (private val buyDao: BuyDao, private val buyRemote: BuyRemote) {
private val job = SupervisorJob()
private val scope = CoroutineScope(Dispatchers.Default + job)
companion object {
//For singleton instantiation
@Volatile private var instance: BuyRepository? = null
fun getInstance(buyDao: BuyDao, buyRemote: BuyRemote) =
instance ?: synchronized(this) {
instance ?: BuyRepository(buyDao, buyRemote)
.also { instance = it}
}
}
suspend fun getBuys(){
refresh()
}
private suspend fun refresh(){
try {
val list = scope.async { buyRemote.loadBuys() }
list.await().forEach { buy -> insert(buy) }
} catch (e: Throwable) {}
}
@WorkerThread
private fun insert(buy: Buy) {
buyDao.insertBuy(buy)
}
}
片段工作,显示数据,当我点击该片段(recyclerView)中的一个项目时,activity 显示详细数据。但是 none 对 activity 的点击有效,就像它没有检测到点击一样。我猜它与协程有关,因为当我注释掉 BuySharedViewModel 中的代码 viewmodelScope.launch { getAllBuys()}
时,它起作用了,因为它从房间数据库中加载了先前调用的数据,并且点击有效。
详细视图中的代码如下:
class BuyDetailActivity : AppCompatActivity() {
private lateinit var sharedViewModel: BuySharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lateinit var buy: Buy
sharedViewModel = ViewModelProviders.of(this).get(BuySharedViewModel::class.java)
val position = intent.getIntExtra("position", 0)
sharedViewModel.allBuys.observe(this, Observer<List<Buy>> { buys ->
buy = buys[position]
val binding: com.example.drake.kunuk.databinding.ActivityBuyDetailBinding =
DataBindingUtil.setContentView(this, com.example.drake.kunuk.R.layout.activity_buy_detail)
binding.buy = buy
val agentNumber = buy.agentNumber?:"+50937438713"
bnvContactAgent.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
com.example.drake.kunuk.R.id.action_call -> {
val callNumberUri = Uri.parse("tel:$agentNumber")
val callIntent = Intent(Intent.ACTION_DIAL, callNumberUri)
startActivity(callIntent)
}
com.example.drake.kunuk.R.id.action_sms -> {
val smsNumberUri = Uri.parse("sms:$agentNumber")
val smsIntent = Intent(Intent.ACTION_SENDTO, smsNumberUri)
startActivity(smsIntent)
}
com.example.drake.kunuk.R.id.action_email -> {
val uriText = "mailto:drakecolin@gmail.com" +
"?subject=" + Uri.encode("I'm interested in $agentNumber") +
"&body=" + Uri.encode("Hello, ")
val uri = Uri.parse(uriText)
val sendIntent = Intent(Intent.ACTION_SENDTO)
sendIntent.data = uri
startActivity(Intent.createChooser(sendIntent, "Send email"))
}
}
false
}
这是我的片段代码:
class BuyFragment : Fragment() {
companion object {
fun newInstance() = BuyFragment()
}
private lateinit var viewModel: BuySharedViewModel
private val buyList = ArrayList<Buy>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Get a new or existing ViewModel from the ViewModelProvider.
viewModel = ViewModelProviders.of(this).get(BuySharedViewModel::class.java)
// Add an observer on the LiveData returned by loadAllBuys.
// The onChanged() method fires when the observed data changes and the activity is
// in the foreground.
viewModel.allBuys.observe(this, Observer<List<Buy>> { buys ->
// Update the cached copy of the words in the adapter.
buys?.let { (rvBuy.adapter as BuyAdapter).setBuys(it) }
progressBar.visibility = View.GONE
})
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.buy_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
rvBuy.layoutManager = LinearLayoutManager(context)
rvBuy.adapter = BuyAdapter(activity!!.applicationContext,
R.layout.buy_card, buyList)
progressBar.visibility = View.VISIBLE
}
}
这是 BuyDao 的代码:
@Dao
接口 BuyDao { @Insert(onConflict = OnConflictStrategy.REPLACE) 有趣的插入购买(可变参数购买:购买)
@Update
fun updateBuy(vararg buys: Buy)
@Delete
fun deleteBuys(vararg buys: Buy)
@Query("SELECT * FROM buys")
fun loadAllBuys(): LiveData<List<Buy>>
@Query("DELETE FROM buys")
suspend fun deleteAll()
}
viewModelScope
默认使用 Dispatchers.Main
并且它会阻止您的 UI.
试试这个:
viewmodelScope.launch(Dispatchers.IO) { getAllBuys()}
编辑:
问题是您在更新实时数据时在 BottomNavigation 上设置了监听器,这导致了这个奇怪的问题。
将您的 BuyDetailActivity
代码替换为:
class BuyDetailActivity : AppCompatActivity() {
private lateinit var sharedViewModel: BuySharedViewModel
private var agentNumber = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityBuyDetailBinding =
DataBindingUtil.setContentView(this, R.layout.activity_buy_detail)
binding.buy = Buy()
lateinit var buy: Buy
sharedViewModel = ViewModelProviders.of(this).get(BuySharedViewModel::class.java)
val position = intent.getIntExtra("position", 0)
sharedViewModel.allBuys.observe(this, Observer<List<Buy>> { buys ->
buy = buys[position]
binding.buy = buy
binding.executePendingBindings()
agentNumber = buy.agentNumber
// set animation duration via code, but preferable in your layout files by using the animation_duration attribute
expandableTextView.setAnimationDuration(750L)
// set interpolators for both expanding and collapsing animations
expandableTextView.setInterpolator(OvershootInterpolator())
// or set them separately.
expandableTextView.expandInterpolator = OvershootInterpolator()
expandableTextView.collapseInterpolator = OvershootInterpolator()
// toggle the ExpandableTextView
buttonToggle.setOnClickListener {
buttonToggle.setText(if (expandableTextView.isExpanded) com.example.drake.kunuk.R.string.more else com.example.drake.kunuk.R.string.less)
expandableTextView.toggle()
}
// but, you can also do the checks yourself
buttonToggle.setOnClickListener {
if (expandableTextView.isExpanded) {
expandableTextView.collapse()
buttonToggle.setText(com.example.drake.kunuk.R.string.more)
} else {
expandableTextView.expand()
buttonToggle.setText(com.example.drake.kunuk.R.string.less)
}
}
//Open photoView activity when clicked
ivHouseDetail.setOnClickListener {
applicationContext
.startActivity(
Intent(
applicationContext,
ViewPagerActivity::class.java
)
.putExtra("imageList", buy.propertyImage)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
}
})
findViewById<BottomNavigationView>(R.id.bnvContactAgent)?.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.action_call -> {
Log.e("BIRJU", "Action call")
val callNumberUri = Uri.parse("tel:$agentNumber")
val callIntent = Intent(Intent.ACTION_DIAL, callNumberUri)
startActivity(callIntent)
}
R.id.action_sms -> {
Log.e("BIRJU", "Action SMS")
val smsNumberUri = Uri.parse("sms:$agentNumber")
val smsIntent = Intent(Intent.ACTION_SENDTO, smsNumberUri)
startActivity(smsIntent)
}
R.id.action_email -> {
Log.e("BIRJU", "Action Email")
val uriText = "mailto:drakecolin@gmail.com" +
"?subject=" + Uri.encode("I'm interested in $agentNumber") +
"&body=" + Uri.encode("Hello, ")
val uri = Uri.parse(uriText)
val sendIntent = Intent(Intent.ACTION_SENDTO)
sendIntent.data = uri
startActivity(Intent.createChooser(sendIntent, "Send email"))
}
}
false
}
}
}