cami-la / credit-application-system

Conheça o Spring Boot. Nesse contexto, explore a linguagem de programação Kotlin e entenda como o projeto Spring Data JPA facilita a criação de aplicativos baseados em Spring que usam tecnologias de acesso a dados.
116 stars 226 forks source link

JetPack Compose #16

Open LPATROCINIO1969 opened 1 year ago

LPATROCINIO1969 commented 1 year ago

Olá Camila,

Você poderia me ajudar com uma dúvida no JetPack Compose? Não estou entendendo muito bem a técnica de hoisting. De acordo com o que eu entendi até então, podemos dividir a construção da UI em vários "composes", cada qual uma função guardando uma parte dos componentes gráficos que aparecem na UI. Entretanto, esses composes são stateless, ou seja, não conseguem guardar uma sucessão de estados, voltando ao estado inicial cada vez que se faz uma nova recomposition. Seria isso? Continuando... dividi a construção da UI em uma função principal com uma estrutura Column e dentro uma chamada a 3 funções (fun) que constroem partes distintas da UI. O meu problema é que uma delas tem dois Buttons para serem clicados (previous e next). O objetivo é apresentar uma sucessão de imagens e suas respectivas legendas, podendo navegar para frente ou para trás. Tentei fazer o hoisting passando o controle do estado para a função principal (usando variáveis com remember e mutableState). O problema é quando tento controlar a sucessão das imagens nas funções que ficam na ponta da navegação. Consigo levar a variável de estado para o ponto de navegação na função que fica no final, mas não estou conseguindo atualizá-la de volta na função principal. Como posso fazer isso no parâmentro onClick do Button?

Segue abaixo o código que desenvolvi de forma muito grosseira. Funciona mas não é o que eu quero fazer...

package br.com.luciopp.artwork

import android.graphics.fonts.FontStyle import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonElevation import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import br.com.luciopp.artwork.domain.Picture import br.com.luciopp.artwork.ui.theme.ArtWorkTheme import androidx.compose.runtime.getValue import androidx.compose.runtime.setValue

class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        ArtWorkTheme {
            // A surface container using the 'background' color from the theme
            Surface(
                modifier = Modifier.fillMaxSize(),
                color = MaterialTheme.colorScheme.background
            ) {
                ArtApp()
            }
        }
    }
}

}

@Composable fun ArtApp(modifier: Modifier = Modifier) { val listImage:MutableList = mutableListOf( Picture(R.drawable.tela_the_starry_night_noite_estrelada_de_van_gogh, stringResource(R.string.str_noite_estrelada),stringResource(R.string.str_van_gogh)), Picture(R.drawable.qual_a_relacao_entre_a_arte_e_a_historia_historia_da_arte_20084_orig,stringResource( R.string.str_nascimento_de_venus),stringResource(R.string.str_sandro_boticcelli)), Picture(R.drawable._574165_guernica_et_le_regard_de_picasso_03,stringResource(R.string.str_guernica),stringResource( R.string.str_pablo_picasso)) ) val maxPosition:Int = listImage.size-1 var actualPosition:Int by remember {mutableStateOf(0)} val actualPicture:Picture =listImage.get(actualPosition) Column( modifier = modifier, horizontalAlignment = CenterHorizontally, verticalArrangement = Arrangement.Center ) { ArtWithImage(actualPicture.idImage,modifier= Modifier .fillMaxWidth() .padding(all = 16.dp)) ArtWithLabelOfImage(actualPicture.Title, actualPicture.Artist,modifier= Modifier .fillMaxWidth() .padding(top = 24.dp, bottom = 16.dp)) /ArtWithButton(position=actualPosition,onClick = {actualPosition = walkListImage(position=actualPosition, maxPosition = maxPosition,inFront=false ) }, modifier= Modifier .fillMaxWidth() .padding(top = 20.dp))/ Row(modifier=modifier.fillMaxWidth().padding(bottom=16.dp), horizontalArrangement = Arrangement.Center) { Button(onClick = { if (actualPosition==0) actualPosition=maxPosition else actualPosition-- },modifier=Modifier.size(width = 160.dp, height = 50.dp)) { Text(text =stringResource(id = R.string.str_previous), textAlign = TextAlign.Center, fontStyle = androidx.compose.ui.text.font.FontStyle.Normal ) } Spacer(modifier=Modifier.size(20.dp)) Button(onClick = { if (actualPosition==maxPosition) actualPosition=0 else actualPosition++},modifier=Modifier.size(width = 160.dp, height = 50.dp)) { Text(text=stringResource(id = R.string.str_next), textAlign = TextAlign.Center, fontStyle = androidx.compose.ui.text.font.FontStyle.Normal) } }

}

}

@Composable fun ArtWithImage(idActualImage:Int,modifier:Modifier=Modifier){ var strImage = idActualImage Image(painter = painterResource(id = strImage), contentDescription = null,modifier= modifier) }

@Composable fun ArtWithLabelOfImage(artWorkTitle:String,artWorkArtist:String, modifier:Modifier=Modifier){ Column(modifier=modifier, horizontalAlignment = CenterHorizontally ) { Text(text = artWorkTitle, fontSize = 28.sp, fontWeight = FontWeight.Bold) Text(text = artWorkArtist,fontSize = 20.sp) } }

/@Composable fun ArtWithButton(position:Int, onClick: (Int)-> Unit,modifier: Modifier=Modifier){ Row(modifier=modifier.padding(bottom=16.dp), horizontalArrangement = Arrangement.Center) { Button(onClick = { // Todo// },modifier=Modifier.size(width = 160.dp, height = 50.dp)) { Text(text =stringResource(id = R.string.str_previous), textAlign = TextAlign.Center, fontStyle = androidx.compose.ui.text.font.FontStyle.Normal ) } Spacer(modifier=Modifier.size(20.dp)) Button(onClick = { //TODO// },modifier=Modifier.size(width = 160.dp, height = 50.dp)) { Text(text=stringResource(id = R.string.str_next), textAlign = TextAlign.Center, fontStyle = androidx.compose.ui.text.font.FontStyle.Normal) } } }/

/private fun walkListImage(position:Int, maxPosition:Int,inFront:Boolean):Int{ var actualPosition = position if (inFront){ if (position < maxPosition){ actualPosition++ } else { actualPosition = 1 } } else { if (position > 1){ actualPosition-- } else { actualPosition = maxPosition } } return actualPosition }/

@Preview(showBackground = true) @Composable fun ArtAppPreview() { ArtWorkTheme { ArtApp() } }

cami-la commented 1 year ago

Amigo, boa tarde. Desculpa a demora, estava de férias.

Então, infelizmente Android não é meu forte. hehe Mas vou marcar uma pessoa aqui que se estiver com tempo, poderá te ajudar. Ve, pode ajudar aqui? @falvojr

falvojr commented 1 year ago

Oi @cami-la e @LPATROCINIO1969, primeiramente me desculpem pelo delay imenso para respondê-los, ultimamento está uma correria por aqui, mas demandas boas 🤩

@LPATROCINIO1969 você entendeu corretamente o conceito básico de "hoisting" em Jetpack Compose. A técnica de "hoisting" é uma abordagem que permite mover o estado para componentes superiores visando facilitar o controle, compartilhamento e reutilização.

Podemos pensar em uma solução para o problema que você mencionou e simplificar o código que você forneceu:

  1. Primeiro, é bom isolar o estado e a lógica do botão "anterior" e "próximo" em funções separadas.
  2. Usaremos o hoisting para mover o controle do estado para um nível superior, permitindo que você controle a posição atual da imagem no seu componente principal (ArtApp).

Vamos fazer as alterações:

//...

@composable
fun ArtApp(modifier: Modifier = Modifier) {
    val listImage = listOf(
        //... (sua lista de imagens)
    )

    val maxPosition = listImage.size - 1
    var actualPosition by remember { mutableStateOf(0) }
    val actualPicture = listImage[actualPosition]

    Column(
        modifier = modifier,
        horizontalAlignment = CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        ArtWithImage(actualPicture.idImage, modifier = Modifier.fillMaxWidth().padding(all = 16.dp))
        ArtWithLabelOfImage(actualPicture.Title, actualPicture.Artist, modifier = Modifier.fillMaxWidth().padding(top = 24.dp, bottom = 16.dp))
        ArtWithButton(
            isAtStart = actualPosition == 0,
            isAtEnd = actualPosition == maxPosition,
            onPreviousClick = { if (actualPosition > 0) actualPosition-- else actualPosition = maxPosition },
            onNextClick = { if (actualPosition < maxPosition) actualPosition++ else actualPosition = 0 },
            modifier = Modifier.fillMaxWidth().padding(top = 20.dp)
        )
    }
}

@composable
fun ArtWithButton(
    isAtStart: Boolean,
    isAtEnd: Boolean,
    onPreviousClick: () -> Unit,
    onNextClick: () -> Unit,
    modifier: Modifier = Modifier
) {
    Row(modifier = modifier.padding(bottom = 16.dp), horizontalArrangement = Arrangement.Center) {
        Button(onClick = onPreviousClick, modifier = Modifier.size(width = 160.dp, height = 50.dp)) {
            Text(text = stringResource(id = R.string.str_previous))
        }
        Spacer(modifier = Modifier.size(20.dp))
        Button(onClick = onNextClick, modifier = Modifier.size(width = 160.dp, height = 50.dp)) {
            Text(text = stringResource(id = R.string.str_next))
        }
    }
}

//...

O que fizemos aqui:

  1. Extraímos a lógica dos botões anterior e próximo para o componente ArtWithButton, que agora aceita lambdas para onPreviousClick e onNextClick. Desta forma, a lógica de atualização do estado é controlada por ArtApp, enquanto a apresentação (UI) é isolada em ArtWithButton.
  2. Eliminamos a necessidade da função walkListImage, porque a lógica é agora muito mais direta.

Agora, ArtApp controla completamente o estado da posição atual e passa as ações adequadas para o componente do botão. Isso torna o código mais limpo e a divisão de responsabilidades mais clara.

Veja se essa estratégia faz sentido para o seu contexto, por favor. Acabei não conseguindo testar efetivamente o código, mas acredito que seja uma linha de pensamento válida considerando o que disse, ok? Aproveitando, vou incluir o @EzequielMessore, @igorbag e @pedrox-hs que são mais experts do que eu em Android 🤖

LPATROCINIO1969 commented 1 year ago

Olá Camila e Venilton,

Obrigado pela atenção em relação a esse problema. Acredito que sua solução seja a mais adequada. Confesso que vou precisar rever o código a fim de verificar se essa solução vai funcionar, mas acredito que sim. Já faz algum tempo que trabalhei nele e dei uma solução meio “capenga” na época. Estou tendo alguma dificuldade para trabalhar a ideia de transferir o código de uma função inteira como argumento de outra função. Como sou de muitas gerações anteriores de programação, esse conceito não é muito intuitivo para mim. Tive que parar para participar do bootcamp do Santander e com isso dei alguns passos para trás no sentido de rever a programação baseada em Views. Assim que possível devo voltar ao Jetpack Compose, pois achei sua abordagem muito mais limpa, sintética e poderosa que a programação onerosa e repetitiva das views.

Novamente obrigado pela atenção. Cordialmente,

Lúcio Passos

Enviado do Email para Windows

De: Venilton FalvoJr Enviado:domingo, 20 de agosto de 2023 13:10 Para: cami-la/credit-application-system Cc:LUCIO PASSOS PATROCINIO; Mention Assunto: Re: [cami-la/credit-application-system] JetPack Compose (Issue #16)

Oi @cami-la e @LPATROCINIO1969, primeiramente me desculpem pelo delay imenso para respondê-los, ultimamento está uma correria por aqui, mas demandas boas 🤩 @LPATROCINIO1969 você entendeu corretamente o conceito básico de "hoisting" em Jetpack Compose. A técnica de "hoisting" é uma abordagem que permite mover o estado para componentes superiores visando facilitar o controle, compartilhamento e reutilização. Podemos pensar em uma solução para o problema que você mencionou e simplificar o código que você forneceu:

  1. Primeiro, é bom isolar o estado e a lógica do botão "anterior" e "próximo" em funções separadas.
  2. Usaremos o hoisting para mover o controle do estado para um nível superior, permitindo que você controle a posição atual da imagem no seu componente principal (ArtApp). Vamos fazer as alterações: //...

@composable fun ArtApp(modifier: Modifier = Modifier) { val listImage = listOf( //... (sua lista de imagens) )

val maxPosition = listImage.size - 1
var actualPosition by remember { mutableStateOf(0) }
val actualPicture = listImage[actualPosition]

Column(
    modifier = modifier,
    horizontalAlignment = CenterHorizontally,
    verticalArrangement = Arrangement.Center
) {
    ArtWithImage(actualPicture.idImage, modifier = Modifier.fillMaxWidth().padding(all = 16.dp))
    ArtWithLabelOfImage(actualPicture.Title, actualPicture.Artist, modifier = Modifier.fillMaxWidth().padding(top = 24.dp, bottom = 16.dp))
    ArtWithButton(
        isAtStart = actualPosition == 0,
        isAtEnd = actualPosition == maxPosition,
        onPreviousClick = { if (actualPosition > 0) actualPosition-- else actualPosition = maxPosition },
        onNextClick = { if (actualPosition < maxPosition) actualPosition++ else actualPosition = 0 },
        modifier = Modifier.fillMaxWidth().padding(top = 20.dp)
    )
}

}

@composable fun ArtWithButton( isAtStart: Boolean, isAtEnd: Boolean, onPreviousClick: () -> Unit, onNextClick: () -> Unit, modifier: Modifier = Modifier ) { Row(modifier = modifier.padding(bottom = 16.dp), horizontalArrangement = Arrangement.Center) { Button(onClick = onPreviousClick, modifier = Modifier.size(width = 160.dp, height = 50.dp)) { Text(text = stringResource(id = R.string.str_previous)) } Spacer(modifier = Modifier.size(20.dp)) Button(onClick = onNextClick, modifier = Modifier.size(width = 160.dp, height = 50.dp)) { Text(text = stringResource(id = R.string.str_next)) } } }

//... O que fizemos aqui:

  1. Extraímos a lógica dos botões anterior e próximo para o componente ArtWithButton, que agora aceita lambdas para onPreviousClick e onNextClick. Desta forma, a lógica de atualização do estado é controlada por ArtApp, enquanto a apresentação (UI) é isolada em ArtWithButton.
  2. Eliminamos a necessidade da função walkListImage, porque a lógica é agora muito mais direta. Agora, ArtApp controla completamente o estado da posição atual e passa as ações adequadas para o componente do botão. Isso torna o código mais limpo e a divisão de responsabilidades mais clara. Veja se essa estratégia faz sentido para o seu contexto, por favor. Acabei não conseguindo testar efetivamente o código, mas acredito que seja uma linha de pensamento válida considerando o que disse, ok? Aproveitando, vou incluir o @EzequielMessore, @igorbag e @pedrox-hs que são mais experts do que eu em Android 🤖 — Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>