Skip to content

Commit 93c013c

Browse files
committed
Add observe extensions for Flow to reduce nesting
1 parent 5e75f4d commit 93c013c

36 files changed

+712
-799
lines changed

app/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ android {
5151

5252
kotlinOptions {
5353
freeCompilerArgs = [
54+
"-Xcontext-receivers",
5455
"-Xno-param-assertions",
5556
"-Xno-call-assertions",
5657
"-Xno-receiver-assertions"

app/src/main/java/com/keylesspalace/tusky/AccountsInListFragment.kt

+8-11
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import android.widget.LinearLayout
2424
import androidx.appcompat.widget.SearchView
2525
import androidx.fragment.app.DialogFragment
2626
import androidx.fragment.app.viewModels
27-
import androidx.lifecycle.lifecycleScope
2827
import androidx.preference.PreferenceManager
2928
import androidx.recyclerview.widget.DiffUtil
3029
import androidx.recyclerview.widget.LinearLayoutManager
@@ -40,13 +39,13 @@ import com.keylesspalace.tusky.util.Either
4039
import com.keylesspalace.tusky.util.emojify
4140
import com.keylesspalace.tusky.util.hide
4241
import com.keylesspalace.tusky.util.loadAvatar
42+
import com.keylesspalace.tusky.util.observe
4343
import com.keylesspalace.tusky.util.show
4444
import com.keylesspalace.tusky.util.unsafeLazy
4545
import com.keylesspalace.tusky.util.viewBinding
4646
import com.keylesspalace.tusky.viewmodel.AccountsInListViewModel
4747
import com.keylesspalace.tusky.viewmodel.State
4848
import javax.inject.Inject
49-
import kotlinx.coroutines.launch
5049

5150
private typealias AccountInfo = Pair<TimelineAccount, Boolean>
5251

@@ -104,17 +103,15 @@ class AccountsInListFragment : DialogFragment(), Injectable {
104103
binding.accountsSearchRecycler.layoutManager = LinearLayoutManager(view.context)
105104
binding.accountsSearchRecycler.adapter = searchAdapter
106105

107-
viewLifecycleOwner.lifecycleScope.launch {
108-
viewModel.state.collect { state ->
109-
adapter.submitList(state.accounts.asRightOrNull() ?: listOf())
106+
viewModel.state.observe(viewLifecycleOwner) { state ->
107+
adapter.submitList(state.accounts.asRightOrNull() ?: listOf())
110108

111-
when (state.accounts) {
112-
is Either.Right -> binding.messageView.hide()
113-
is Either.Left -> handleError(state.accounts.value)
114-
}
115-
116-
setupSearchView(state)
109+
when (state.accounts) {
110+
is Either.Right -> binding.messageView.hide()
111+
is Either.Left -> handleError(state.accounts.value)
117112
}
113+
114+
setupSearchView(state)
118115
}
119116

120117
binding.searchView.isSubmitButtonEnabled = true

app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.kt

+80-90
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import com.keylesspalace.tusky.util.Error
4949
import com.keylesspalace.tusky.util.Loading
5050
import com.keylesspalace.tusky.util.Success
5151
import com.keylesspalace.tusky.util.await
52+
import com.keylesspalace.tusky.util.observe
5253
import com.keylesspalace.tusky.util.show
5354
import com.keylesspalace.tusky.util.viewBinding
5455
import com.keylesspalace.tusky.viewmodel.EditProfileViewModel
@@ -152,85 +153,79 @@ class EditProfileActivity : BaseActivity(), Injectable {
152153

153154
viewModel.obtainProfile()
154155

155-
lifecycleScope.launch {
156-
viewModel.profileData.collect { profileRes ->
157-
if (profileRes == null) return@collect
158-
when (profileRes) {
159-
is Success -> {
160-
val me = profileRes.data
161-
if (me != null) {
162-
binding.displayNameEditText.setText(me.displayName)
163-
binding.noteEditText.setText(me.source?.note)
164-
binding.lockedCheckBox.isChecked = me.locked
165-
166-
accountFieldEditAdapter.setFields(me.source?.fields.orEmpty())
167-
binding.addFieldButton.isVisible =
168-
(me.source?.fields?.size ?: 0) < maxAccountFields
169-
170-
if (viewModel.avatarData.value == null) {
171-
Glide.with(this@EditProfileActivity)
172-
.load(me.avatar)
173-
.placeholder(R.drawable.avatar_default)
174-
.transform(
175-
FitCenter(),
176-
RoundedCorners(
177-
resources.getDimensionPixelSize(R.dimen.avatar_radius_80dp)
178-
)
156+
viewModel.profileData.observe { profileRes ->
157+
if (profileRes == null) return@observe
158+
when (profileRes) {
159+
is Success -> {
160+
val me = profileRes.data
161+
if (me != null) {
162+
binding.displayNameEditText.setText(me.displayName)
163+
binding.noteEditText.setText(me.source?.note)
164+
binding.lockedCheckBox.isChecked = me.locked
165+
166+
accountFieldEditAdapter.setFields(me.source?.fields.orEmpty())
167+
binding.addFieldButton.isVisible =
168+
(me.source?.fields?.size ?: 0) < maxAccountFields
169+
170+
if (viewModel.avatarData.value == null) {
171+
Glide.with(this@EditProfileActivity)
172+
.load(me.avatar)
173+
.placeholder(R.drawable.avatar_default)
174+
.transform(
175+
FitCenter(),
176+
RoundedCorners(
177+
resources.getDimensionPixelSize(R.dimen.avatar_radius_80dp)
179178
)
180-
.into(binding.avatarPreview)
181-
}
182-
183-
if (viewModel.headerData.value == null) {
184-
Glide.with(this@EditProfileActivity)
185-
.load(me.header)
186-
.into(binding.headerPreview)
187-
}
179+
)
180+
.into(binding.avatarPreview)
181+
}
182+
183+
if (viewModel.headerData.value == null) {
184+
Glide.with(this@EditProfileActivity)
185+
.load(me.header)
186+
.into(binding.headerPreview)
188187
}
189188
}
190-
is Error -> {
191-
Snackbar.make(
192-
binding.avatarButton,
193-
R.string.error_generic,
194-
Snackbar.LENGTH_LONG
195-
)
196-
.setAction(R.string.action_retry) {
197-
viewModel.obtainProfile()
198-
}
199-
.show()
200-
}
201-
is Loading -> { }
202189
}
190+
is Error -> {
191+
Snackbar.make(
192+
binding.avatarButton,
193+
R.string.error_generic,
194+
Snackbar.LENGTH_LONG
195+
)
196+
.setAction(R.string.action_retry) {
197+
viewModel.obtainProfile()
198+
}
199+
.show()
200+
}
201+
is Loading -> { }
203202
}
204203
}
205204

206-
lifecycleScope.launch {
207-
viewModel.instanceData.collect { instanceInfo ->
208-
maxAccountFields = instanceInfo.maxFields
209-
accountFieldEditAdapter.setFieldLimits(
210-
instanceInfo.maxFieldNameLength,
211-
instanceInfo.maxFieldValueLength
212-
)
213-
binding.addFieldButton.isVisible =
214-
accountFieldEditAdapter.itemCount < maxAccountFields
215-
}
205+
viewModel.instanceData.observe { instanceInfo ->
206+
maxAccountFields = instanceInfo.maxFields
207+
accountFieldEditAdapter.setFieldLimits(
208+
instanceInfo.maxFieldNameLength,
209+
instanceInfo.maxFieldValueLength
210+
)
211+
binding.addFieldButton.isVisible =
212+
accountFieldEditAdapter.itemCount < maxAccountFields
216213
}
217214

218215
observeImage(viewModel.avatarData, binding.avatarPreview, true)
219216
observeImage(viewModel.headerData, binding.headerPreview, false)
220217

221-
lifecycleScope.launch {
222-
viewModel.saveData.collect {
223-
if (it == null) return@collect
224-
when (it) {
225-
is Success -> {
226-
finish()
227-
}
228-
is Loading -> {
229-
binding.saveProgressBar.visibility = View.VISIBLE
230-
}
231-
is Error -> {
232-
onSaveFailure(it.errorMessage)
233-
}
218+
viewModel.saveData.observe {
219+
if (it == null) return@observe
220+
when (it) {
221+
is Success -> {
222+
finish()
223+
}
224+
is Loading -> {
225+
binding.saveProgressBar.visibility = View.VISIBLE
226+
}
227+
is Error -> {
228+
onSaveFailure(it.errorMessage)
234229
}
235230
}
236231
}
@@ -258,10 +253,8 @@ class EditProfileActivity : BaseActivity(), Injectable {
258253
}
259254

260255
onBackPressedDispatcher.addCallback(this, onBackCallback)
261-
lifecycleScope.launch {
262-
viewModel.isChanged.collect { dataWasChanged ->
263-
onBackCallback.isEnabled = dataWasChanged
264-
}
256+
viewModel.isChanged.observe { dataWasChanged ->
257+
onBackCallback.isEnabled = dataWasChanged
265258
}
266259
}
267260

@@ -277,26 +270,23 @@ class EditProfileActivity : BaseActivity(), Injectable {
277270
imageView: ImageView,
278271
roundedCorners: Boolean
279272
) {
280-
lifecycleScope.launch {
281-
flow.collect { imageUri ->
282-
283-
// skipping all caches so we can always reuse the same uri
284-
val glide = Glide.with(imageView)
285-
.load(imageUri)
286-
.skipMemoryCache(true)
287-
.diskCacheStrategy(DiskCacheStrategy.NONE)
288-
289-
if (roundedCorners) {
290-
glide.transform(
291-
FitCenter(),
292-
RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_80dp))
293-
).into(imageView)
294-
} else {
295-
glide.into(imageView)
296-
}
297-
298-
imageView.show()
273+
flow.observe { imageUri ->
274+
// skipping all caches so we can always reuse the same uri
275+
val glide = Glide.with(imageView)
276+
.load(imageUri)
277+
.skipMemoryCache(true)
278+
.diskCacheStrategy(DiskCacheStrategy.NONE)
279+
280+
if (roundedCorners) {
281+
glide.transform(
282+
FitCenter(),
283+
RoundedCorners(resources.getDimensionPixelSize(R.dimen.avatar_radius_80dp))
284+
).into(imageView)
285+
} else {
286+
glide.into(imageView)
299287
}
288+
289+
imageView.show()
300290
}
301291
}
302292

app/src/main/java/com/keylesspalace/tusky/ListsActivity.kt

+7-12
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import androidx.activity.viewModels
2828
import androidx.annotation.StringRes
2929
import androidx.appcompat.app.AlertDialog
3030
import androidx.core.widget.doOnTextChanged
31-
import androidx.lifecycle.lifecycleScope
3231
import androidx.recyclerview.widget.DiffUtil
3332
import androidx.recyclerview.widget.DividerItemDecoration
3433
import androidx.recyclerview.widget.LinearLayoutManager
@@ -42,6 +41,7 @@ import com.keylesspalace.tusky.di.ViewModelFactory
4241
import com.keylesspalace.tusky.entity.MastoList
4342
import com.keylesspalace.tusky.util.BindingHolder
4443
import com.keylesspalace.tusky.util.hide
44+
import com.keylesspalace.tusky.util.observe
4545
import com.keylesspalace.tusky.util.show
4646
import com.keylesspalace.tusky.util.startActivityWithSlideInAnimation
4747
import com.keylesspalace.tusky.util.viewBinding
@@ -56,7 +56,6 @@ import com.keylesspalace.tusky.viewmodel.ListsViewModel.LoadingState.LOADING
5656
import dagger.android.DispatchingAndroidInjector
5757
import dagger.android.HasAndroidInjector
5858
import javax.inject.Inject
59-
import kotlinx.coroutines.launch
6059

6160
// TODO use the ListSelectionFragment (and/or its adapter or binding) here; but keep the LoadingState from here (?)
6261

@@ -95,23 +94,19 @@ class ListsActivity : BaseActivity(), Injectable, HasAndroidInjector {
9594
binding.swipeRefreshLayout.setOnRefreshListener { viewModel.retryLoading() }
9695
binding.swipeRefreshLayout.setColorSchemeResources(R.color.tusky_blue)
9796

98-
lifecycleScope.launch {
99-
viewModel.state.collect(this@ListsActivity::update)
100-
}
97+
viewModel.state.observe(this@ListsActivity::update)
10198

10299
viewModel.retryLoading()
103100

104101
binding.addListButton.setOnClickListener {
105102
showlistNameDialog(null)
106103
}
107104

108-
lifecycleScope.launch {
109-
viewModel.events.collect { event ->
110-
when (event) {
111-
Event.CREATE_ERROR -> showMessage(R.string.error_create_list)
112-
Event.UPDATE_ERROR -> showMessage(R.string.error_rename_list)
113-
Event.DELETE_ERROR -> showMessage(R.string.error_delete_list)
114-
}
105+
viewModel.events.observe { event ->
106+
when (event) {
107+
Event.CREATE_ERROR -> showMessage(R.string.error_create_list)
108+
Event.UPDATE_ERROR -> showMessage(R.string.error_rename_list)
109+
Event.DELETE_ERROR -> showMessage(R.string.error_delete_list)
115110
}
116111
}
117112
}

0 commit comments

Comments
 (0)