I was trying to achieve the below layout
I tried using Row(Modifier.weight(50f)) that's when the compiler start throwing
If imported from ColumnInstance - import androidx.compose.foundation.layout.ColumnScopeInstance.weight
Cannot access 'ColumnScopeInstance': it is internal in 'androidx.compose.foundation.layout'
If imported from RowInstance - androidx.compose.foundation.layout.RowScopeInstance.weight
Cannot access 'RowScopeInstance': it is internal in 'androidx.compose.foundation.layout'
Attaching my Composable code below
#Composable
fun BoxLayout(){
Row(Modifier.weight(50f)) {
BoxWithText()
BoxWithText()
}
}
Attaching entire file for reference
package me.sanjaykapilesh.layoutmastery
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScopeInstance.weight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import me.sanjaykapilesh.layoutmastery.ui.theme.LayoutMasteryTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
LayoutMasteryTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
BoxWithText()
}
}
}
}
}
#Composable
fun BoxLayout(){
Row(Modifier.weight(50f)) {
BoxWithText()
BoxWithText()
}
}
#Composable
fun BoxWithText() {
Column() {
Text(text = "Hello Box!")
Text(text = "Displays text and follows Material Design guidelines")
}
}
#Preview(showBackground = true)
#Composable
fun BoxLayoutPreview() {
LayoutMasteryTheme {
BoxLayout()
}
}
I am not sure why I am getting an error. I am also unable to achieve Modifier.weight
Question - https://developer.android.com/codelabs/basic-android-kotlin-compose-composables-practice-problems?authuser=2&continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fandroid-basics-compose-unit-1-pathway-3%3Fauthuser%3D2%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fbasic-android-kotlin-compose-composables-practice-problems#3
You can use an extension function to get the context. For example:
#Composable
fun ColumnScope.BoxLayout(){
Row(Modifier.weight(50f)) {
BoxWithText()
BoxWithText()
}
}
Some modifiers are unique to scopes that they are defined in like Modifier.weight is only available in RowScope or ColumnScope by default. Or Modifier.align is only available inside BoxScope.
When you wish to access these Modifiers you either need to have your Composables functions in these scopes or create a function that takes #Composable argument with Receiver of these scopes
#Composable
fun BoxLayout(){
Row(Modifier.weight(50f)) {
BoxWithText()
BoxWithText()
}
}
BoxLayout should return RowScope/ColumnScope as this to be able to use Modifier.weight and this can be done as
#Composable
fun BoxWithLayout(content: #Composable RowScope.()->Unit){
Row {
content()
}
}
#Composable
private fun Sample() {
BoxWithLayout {
Row(Modifier.weight(50f)) {
BoxWithText()
BoxWithText()
}
}
}
I've had problems like that too.
trouble
You can try to block "RowScope" and then press Alt + Enter select "Surround with widget" ended select "Surround with column"
solution
Related
I want to disable black shadow when we disable Ripple effect on user click action. I tried from this answer 1 and answer 2. I made a github project to see the code.
MainActivity.kt
package com.example.disableshadow
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.ripple.LocalRippleTheme
import androidx.compose.material.ripple.RippleAlpha
import androidx.compose.material.ripple.RippleTheme
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalMinimumTouchTargetEnforcement
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import com.example.disableshadow.ui.theme.DisableShadowTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
DisableShadowTheme {
// A surface container using the 'background' color from the theme
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Greeting()
}
}
}
}
}
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun Greeting() {
val interactionSource = remember { MutableInteractionSource() }
CompositionLocalProvider(
LocalMinimumTouchTargetEnforcement provides false,
LocalRippleTheme provides CustomRippleTheme(Color.Unspecified),
content = {
Surface(
onClick = { Log.e("ItemDisablePreview", "ItemClicked") },
interactionSource = interactionSource,
color = Color.White,
) {
Text(text = "Item Name")
}
}
)
}
#Preview(showBackground = true)
#Composable
fun DefaultPreview() {
DisableShadowTheme {
Greeting()
}
}
class CustomRippleTheme(private val rippleColor: Color) : RippleTheme {
#Composable
override fun defaultColor(): Color {
return if (rippleColor == Color.Unspecified) {
Color.Unspecified
} else {
RippleTheme.defaultRippleColor(
contentColor = rippleColor,
lightTheme = true
)
}
}
#Composable
override fun rippleAlpha() = when (rippleColor) {
Color.Red -> {
RippleAlpha(1f, 1f, 1f, 1f)
}
Color.Unspecified -> {
RippleAlpha(0.0f, 0.0f, 0.0f, 0.0f)
}
else -> {
RippleTheme.defaultRippleAlpha(
contentColor = rippleColor,
lightTheme = true
)
}
}
}
Image when we press.
Need a bit of help on why data from viewmodel is not shown in the composable function MainContent. I tried to use MVVM style with coroutine but without DI which I think will be easier but somehow, I could not get it to work.
The viewmodel is working as the log.d is showing the correct data from server but somehow, I could not get it to display in
Text(text = viewModel.posts[it].phrase)
Any help will be greatly appreciated. The github link for this program is in https://github.com/somaria/LearnChnCompose
package com.gamecrawl.learnchncompose
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.ViewModel
import com.gamecrawl.learnchncompose.ui.theme.LearnChnComposeTheme
import io.ktor.client.*
import io.ktor.client.engine.android.*
import io.ktor.client.features.*
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
import io.ktor.client.features.logging.*
import io.ktor.client.request.*
import io.ktor.http.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel: MainViewModel by viewModels()
setContent {
LearnChnComposeTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
MainContent(viewModel)
}
}
}
}
}
class MainViewModel : ViewModel() {
private var _posts = mutableListOf(Post("12", "test phrase", true))
var posts get() = _posts; set(value) {
_posts = value
}
init {
CoroutineScope(Dispatchers.IO).launch {
_posts = KtorClient.httpClient.get("https://learnchn.herokuapp.com/") {
header("Content-Type", "application/json")
}
Log.d("HomeViewModel", "init: ${_posts[1].phrase}")
Log.d("HomeViewModel", "init: ${_posts[1].id}")
}
}
fun addPost(post: Post) {
CoroutineScope(Dispatchers.IO).launch {
val addedpost: Post = KtorClient.httpClient.post("https://learnchn.herokuapp.com/add") {
header("Content-Type", "application/json")
body = post
}
}
}
}
#Composable
fun MainContent(viewModel: MainViewModel) {
Column {
LazyColumn {
items(viewModel.posts.size) {
Text(text = viewModel.posts[it].phrase)
}
}
Button(onClick = {
viewModel.addPost(Post("test", "adding post 222", true))
}) {
Text(text = "Add Post")
}
}
}
#Serializable
data class Post(
val id: String,
val phrase: String,
val published: Boolean
)
object KtorClient {
val json = Json {
encodeDefaults = true
ignoreUnknownKeys = true
isLenient = true
}
val httpClient = HttpClient(Android) {
install(HttpTimeout) {
socketTimeoutMillis = 200000
requestTimeoutMillis = 200000
connectTimeoutMillis = 200000
}
install(Logging) {
logger = object : Logger {
override fun log(message: String) {
Log.d("TAG", "log: $message")
}
}
}
install(JsonFeature) {
serializer = KotlinxSerializer(json)
}
defaultRequest {
contentType(ContentType.Application.Json)
accept(ContentType.Application.Json)
}
}
}
The data type of the posts is a MutableList<Post>. This means that changes to this variable will not cause the function to recompose. When the UI is loaded, then the variable does not have any data, since you fetch the data in an asynchronous coroutine. However, when the variable is updated, the UI is not recomposed.
To fix this issue, you must declare _posts to be a MutableState<List<Post>> from the compose library instead. Reconfigure your ViewModel in the following way:
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
class MainViewModel : ViewModel() {
private val _posts = mutableStateOf(listOf<Post>()) // <- requires init value
val posts: State<List<Post>> = _posts // <- keep both variables immutable 'val'
/* always expose the immutable form of State */
init {
CoroutineScope(Dispatchers.IO).launch {
/* _posts.value is used now due to the datatype change */
_posts.value = KtorClient.httpClient.get("https://learnchn.herokuapp.com/") {
header("Content-Type", "application/json")
}
Log.d("HomeViewModel", "init: ${_posts.value[1].phrase}")
Log.d("HomeViewModel", "init: ${_posts.value[1].id}")
}
}
fun addPost(post: Post) {
CoroutineScope(Dispatchers.IO).launch {
val addedpost: Post = KtorClient.httpClient.post("https://learnchn.herokuapp.com/add") {
header("Content-Type", "application/json")
body = post
}
}
}
}
Now since your public posts variable is of type State<T>, you need to make changes to your composable function:
#Composable
fun MainContent(viewModel: MainViewModel) {
val posts = viewModel.posts.value // <- grab the value of the state variable.
/* The function will recompose whenever there's a change in posts */
Column {
LazyColumn {
items(posts.size) {
Text(text = posts[it].phrase)
}
}
Button(onClick = {
viewModel.addPost(Post("test", "adding post 222", true))
}) {
Text(text = "Add Post")
}
}
}
This should help your issue.
Eventough I haven't used remember keyword values are still remembered between recomposition.
While tutorials say that it shouldn't.
I am trying to understand all this mess around Compose State Variables but things work differently then explained.
package com.example.testcompose
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
var state by mutableStateOf(1)
Button({state += 1 }) { Text("$state") }
}
}
}
Here is extended code which demonstrated that Button and Text behave differently.
In both cases I am NOT using remember.
But Button is behaving as if I am using rmemeber.
//==================================================================
// MAIN ACTIVITY
//==================================================================
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Column {
MyButton() //Behaves incorrectly like there is remember
MyText() //Behaves correctly not remembering the value
}
}
}
}
#Composable
fun MyButton(){
Column {
var state by mutableStateOf("Hello ")
Button({ state += "you " }) { Text(state) }
}
}
#Composable
fun MyText(){
Column {
var state by mutableStateOf("Hello ")
Text(state, modifier = Modifier.clickable(onClick = { state += "you " } ) )
}
}
As mentioned by Google:
Recomposition is the process of calling your composable functions
again when inputs change. This happens when the function's inputs
change. When Compose recomposes based on new inputs, it only calls the
functions or lambdas that might have changed, and skips the rest. By
skipping all functions or lambdas that don't have changed parameters,
Compose can recompose efficiently.
Let's change your code to this:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Column {
MyComposable()
}
}
}
}
#Composable
fun MyComposable() {
Column {
var state1 by mutableStateOf("Hello ")
Button({ state1 += "you " }) { Text(state1) }
var state2 by mutableStateOf("Hello ")
Text(state2, modifier = Modifier.clickable(onClick = { state2 += "you " }))
}
}
You will see that even though button behaves correctly, text changes both values due to recomposition. So as mentioned by Google because of the underline mechanism of Compose it is unpredictable how the code runs or might skip part of the execution of the code. So you should always use remember on this situations in order to have the expected outcome.
Please read carefully Thinking in Compose.
I am creating a SnackBar with action using Android Jetpack Compose.
My requirement is, when accessibility TalkBack is enabled and snackBar is shown, action button should be focused, so that user can perform action (action button click) by clicking (double tap)anywhere.
I just provide my prototype. I added all code in one activity, to simplify example. I suspect you can improve and modify for your case. May be my example will inspire you)
package com.rollo.exampleandtests.composable
import android.os.Bundle
import android.util.Log
import android.view.MotionEvent
import android.view.MotionEvent.ACTION_DOWN
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.rollo.exampleandtests.composable.ui.ComposeTutorialTheme
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
class ComposeActivity10 : AppCompatActivity() {
//must be moved to ViewModel
private var isShowed = false
private var action: MutableStateFlow<Boolean?> = MutableStateFlow(null)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeTutorialTheme {
val forceDismiss by action.collectAsState()
SnackBarDemo(forceDismiss) {
isShowed = it
}
}
}
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
return if (ev?.action == ACTION_DOWN && isShowed) {
action.value = !(action.value ?: false)
true
} else {
super.dispatchTouchEvent(ev)
}
}
}
#Composable
fun SnackBarDemo(action: Boolean?, callback: (Boolean) -> Unit) {
val coroutineScope = rememberCoroutineScope()
val scaffoldState = rememberScaffoldState()
Scaffold(
modifier = Modifier.fillMaxSize(),
scaffoldState = scaffoldState
) {
Button(
modifier = Modifier
.fillMaxWidth()
.padding(20.dp),
onClick = {
coroutineScope.launch {
callback(true)
val snackbarResult = scaffoldState.snackbarHostState.showSnackbar(
message = "This is your message",
actionLabel = "Do something."
)
callback(false)
when (snackbarResult) {
SnackbarResult.Dismissed -> Log.d("SnackbarDemo", "Dismissed")
SnackbarResult.ActionPerformed -> Log.d(
"SnackbarDemo",
"Snackbar's button clicked"
)
}
}
}
) {
Text(text = "A button that shows a Snackbar")
}
}
LaunchedEffect(key1 = action, block = {
if (action == true) {
scaffoldState.snackbarHostState.currentSnackbarData?.dismiss()
//HERE: do something
}
})
}
Minimal reproducable example, so that after screen rotation the shown random value changed :
package composesandbox
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.*
val rnd = Random()
suspend fun loadRandomNumber(): Int {
delay(1000L) //emulating network delay
return rnd.nextInt(1000)
}
class RandomNumberViewModel : ViewModel() {
val num = mutableStateOf(0)
fun reload() {
viewModelScope.launch {
num.value = loadRandomNumber()
}
}
}
#Composable
fun RandomNumberScreen() {
val vm = viewModel<RandomNumberViewModel>()
Column {
LaunchedEffect(Unit) {
vm.reload()
}
Text(text = "Random Number:\n ${vm.num.value}")
}
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
RandomNumberScreen()
}
}
}
I want the data (viewmodel.num) to be only loaded ONCE despite the screen rotations. Please explain, why data is reloaded on rotations.
On a screen rotation onCreate gets called again.
This means everything inside the Activity is recomposed.
On Recompose your Composed function RandomNumberScreen() is called again and this calls your reload() function again, which changes the value of num.
Keep in mind, that on a recompose the whole Composable gets executed again to keep the State up to date.
Your intention might be, that everythime num changes, the Composable should recompose. You should not trigger a function on recompose which changes num again.
You could use something like a variable in the viewmodel to make sure num is initialized and doesn't change when initialized. check if the variable is true or false before executing reload.
if (!isInitialized) reload() something like this.
It is because you are calling the reload method inside a LaunchedEffect, which is executed upon the first composition. When you rotate the screen, the entire screen is destroyed. All the Composables re-render themselves and hence, the inconsistent value.
Ok so you could do something like this
class RandomNumberViewModel : ViewModel() {
val num = mutableStateOf(0)
fun reload(): Int { //return something so that it can be saved
viewModelScope.launch {
num.value = loadRandomNumber()
}
return 0
}
}
#Composable
fun RandomNumberScreen() {
val vm = viewModel<RandomNumberViewModel>()
Column {
rememberSaveable {
vm.reload()
}
Text(text = "Random Number:\n ${vm.num.value}")
}
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
RandomNumberScreen()
}
}
}