Hyun
06/25/2020, 5:08 PMif (shown) {
Column {
Text("Success")
}
}
but, I would like to use the way below similar to data binding.(android:visiblity="@{model.shown}"
)
as it reduce indent depth, so, seems easy to read UI code. and feel better to separate ui and logic.
Column(Modifier.visible(shown)) {
Text("Success")
}
I tried to customize Modifier. but, it looks difficult.
is there any possible solution?
or is it not recommended approach?Timo Drick
06/25/2020, 5:12 PMModifier.drawOpacity()
But i think the child is just invisible when you set it to 0Timo Drick
06/25/2020, 5:14 PM@Composable
fun Modifier.expandable(
id: Any?,
collapsed: CollapseState,
onChanged: (CollapseState) -> Unit = {},
duration: Int = 500
) = collapseable(id, collapsed, onChanged, duration, CollapseState.COLLAPSED)
@Composable
fun Modifier.collapseable(
id: Any?,
collapsed: CollapseState,
onChanged: (CollapseState) -> Unit = {},
duration: Int = 500,
initialState: CollapseState = CollapseState.EXPANDED
) = composed {
val animatedHeight = animatedValue(initVal = 0, converter = IntPxToVectorConverter)
var isRunning by stateFor(id) { false }
val originalHeightState = stateFor(id) { 0 }
var currentState by stateFor(id) { initialState }
val heightState = stateFor(id) { 0 }
if (isRunning.not()) animatedHeight.snapTo(if (currentState == CollapseState.EXPANDED) heightState.value else 0)
if (collapsed != currentState) {
//State change requested
val animation = TweenBuilder<Int>().apply { this.duration = duration }
isRunning = true
if (collapsed == CollapseState.COLLAPSED) {
// Collapse requested
animatedHeight.animateTo(
0,
onEnd = { _, _ -> onChanged(CollapseState.COLLAPSED); isRunning = false })
} else {
animatedHeight.animateTo(
originalHeightState.value,
animation,
onEnd = { _, _ -> onChanged(CollapseState.EXPANDED); isRunning = false })
}
currentState = collapsed
}
CollapseableModifier(originalHeightState, animatedHeight, heightState)
}
private data class CollapseableModifier(
val originalHeightState: MutableState<Int>,
val animatedHeight: AnimatedValue<Int, AnimationVector1D>,
val height: MutableState<Int>
) : LayoutModifier {
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints,
layoutDirection: LayoutDirection
): MeasureScope.MeasureResult {
val childConstraints =
constraints.copy(maxHeight = Int.MAX_VALUE, maxWidth = constraints.maxWidth)
val placeable = measurable.measure(childConstraints)
originalHeightState.value = placeable.height
val width = min(placeable.width, constraints.maxWidth)
height.value = min(placeable.height, constraints.maxHeight)
return layout(width, animatedHeight.value) {
placeable.place(0, 0)
}
}
}
Maybe you could modify it for your needsLeland Richardson [G]
06/25/2020, 5:15 PMLeland Richardson [G]
06/25/2020, 5:15 PMLeland Richardson [G]
06/25/2020, 5:15 PMand feel better to separate ui and logic.
Leland Richardson [G]
06/25/2020, 5:15 PMSiyamed
06/25/2020, 5:16 PMLeland Richardson [G]
06/25/2020, 5:16 PMSiyamed
06/25/2020, 5:17 PMLeland Richardson [G]
06/25/2020, 5:17 PMTimo Drick
06/25/2020, 5:26 PMHyun
06/25/2020, 5:35 PMseparating ui and logic
.
it may be just my feeling which originated from layout and data binding experience.
but, as you give me opportunity to explain.
currently I’m thinking what is the best approach to use compose.
and I felt that there is possibility that developers add logic on ui carelessly. though, if the architecture is well organized, it has no issues.
so, I have thought that how to separate ui and logic.
and I considered layout data binding approach.
it doesn’t change ui format. just change attributes.
so, developer can read ui code easy. and logic side can focus on variable only.
2. I’m not sure what is the best approach. but in my short experience. if it’s possible. it seems good to use same way with View.Visible, View.Gone, View.Invisible.
but. if it’s difficult, gone and visible also be good.Hyun
06/25/2020, 5:41 PMTimo Drick
06/26/2020, 11:10 PMenum class Visibility {
VISIBLE, INVISIBLE, GONE
}
@Composable
fun Modifier.visibility(visibility: Visibility) = this + VisibleModifier(visibility)
private data class VisibleModifier(val visibility: Visibility) : LayoutModifier {
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints,
layoutDirection: LayoutDirection
): MeasureScope.MeasureResult {
return if (visibility == Visibility.GONE) {
layout(0, 0) {
// Empty placement block
}
} else {
val placeable = measurable.measure(constraints)
layout(placeable.width, placeable.height) {
if (visibility == Visibility.VISIBLE) {
placeable.place(0, 0)
}
}
}
}
}
Hyun
06/27/2020, 2:41 AMHyun
06/27/2020, 6:50 PMvar resource by state<Resource<String>> { Resource.Loading }
Column(Modifier.visible(visible = resource.isSuccess()) {
Text(resource.successData)// this convert success data as not null String
}
Though the Text(resource.successData)
is shown only on success case, yet Text(resource.successData)
is processed even if visibility is gone.
so, I have to check null if the success data exists or not like Text(resource.data?:"")
as modifier mentioned that it will shows only success case.
I would like not to handle the case that data not exists.
is it possible not to process sub view if visibility is gone?Leland Richardson [G]
06/27/2020, 8:50 PMHyun
06/27/2020, 9:09 PMif
seems proper approach 🙂
if Column
doesn’t exits on the above example.
we can’t use Modifier.visible()
then it’s better not to use it.Timo Drick
06/28/2020, 2:17 AM@Composable
fun ImageView(url: String) {
var retryCounter by stateFor(url) { 0 }
val imageState = ImageLoader.loadImageUI(url)
//val state = imageState
InitializedCrossfade(current = imageState) { state ->
when (state) {
is LoadingState.Start -> Box(Modifier.fillMaxSize(), backgroundColor = MaterialTheme.colors.surface,gravity = Alignment.Center)
is LoadingState.Loading -> LoadingBox()
is LoadingState.Success -> Image(asset = state.data, modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop)
is LoadingState.Error -> ErrorBox(onClick = { retryCounter++ })
}
}
}
Also a general wrapper to do stuff on a coroutine and get the loading state:
sealed class LoadingState<out T: Any> {
object Start: LoadingState<Nothing>()
object Loading: LoadingState<Nothing>()
class Error(val error: Exception): LoadingState<Nothing>()
class Success<T: Any>(val data: T): LoadingState<T>()
}
@Composable
fun <T: Any> loadingStateFor(vararg inputs: Any?, initBlock: () -> LoadingState<T> = { LoadingState.Start },
loadingBlock: suspend CoroutineScope.() -> T): LoadingState<T> {
var state by stateFor(*inputs, init = initBlock)
if (state !is LoadingState.Success) {
launchInComposition(*inputs) {
val loadingSpinnerDelay = async {
delay(500)
state = LoadingState.Loading
}
state = try {
LoadingState.Success(loadingBlock())
} catch (err: Exception) {
LoadingState.Error(err)
} finally {
loadingSpinnerDelay.cancelAndJoin()
}
}
}
return state
}