Tentando Jetpack Compose em batalha?

Finalmente, chegou o momento em que você não precisa criar o Android Studio para experimentar a nova estrutura declarativa da interface do usuário para o Android. O Jetpack Compose agora está disponível como a primeira visualização do desenvolvedor no repositório Maven do Google. Com esta notícia, minha segunda-feira de manhã começou. E imediatamente houve um desejo de ver que conjunto de ferramentas eles estavam esperando.



Decidi começar meu conhecido imediatamente com uma tentativa de introduzi-lo no projeto de estimação publicado no Google Play. Além disso, durante muito tempo eu quis fazer uma página “Sobre o aplicativo”. Neste artigo, falarei sobre os principais componentes e etapas da conexão do Compose:


  1. Conexão de dependência
  2. Temas e estilos. Integração com os existentes no projeto.
  3. Testes de acessibilidade e interface do usuário.
  4. Os principais componentes e análogos dos herdeiros da View.
  5. Trabalhe com o Estado.

Conexão de dependência


Para começar, atualizei o estúdio de 3.5 para 3.5.1 (em vão), adicionei dependências básicas. Uma lista completa pode ser vista em um artigo de Cyril .


// build.gradle ext.compose_version= '0.1.0-dev01' //build.gradle  dependencies{ ... implementation "androidx.compose:compose-runtime:$compose_version" kapt "androidx.compose:compose-compiler:$compose_version" implementation "androidx.ui:ui-layout:$compose_version" implementation "androidx.ui:ui-android-text:$compose_version" implementation "androidx.ui:ui-text:$compose_version" implementation "androidx.ui:ui-material:$compose_version" } 

E então eu tentei coletar tudo isso por causa das versões dispersas do Firebase. Após o que eu me deparei com os obstáculos de composição:


 app/src/main/AndroidManifest.xml Error: uses-sdk:minSdkVersion 16 cannot be smaller than version 21 declared in library [androidx.ui:ui-layout:0.1.0-dev01] .../ui-layout-0.1.0-dev01/AndroidManifest.xml as the library might be using APIs not available in 16 Suggestion: use a compatible library with a minSdk of at most 16, or increase this project's minSdk version to at least 21, or use tools:overrideLibrary="androidx.ui.layout" to force usage (may lead to runtime failures) 

Sim, o Compose estava disponível apenas com o minSdk 21 (Lolipop). Talvez essa seja uma medida temporária, mas esperava-se que ela suportasse versões anteriores do sistema operacional.


Mas isso não é tudo. O Redigir funciona no Reflection, em vez do Plugin do compilador Kotlin, como indicado anteriormente, por exemplo, aqui . Portanto, para que tudo comece, você precisa adicionar o Kotlin Reflect também dependendo de:


 implementation "org.jetbrains.kotlin:kotlin-reflect" 

Bem, para a sobremesa. Compor O dp implementa funções de extensão para Int, Long, Float, marcadas com a palavra-chave inline. Isso pode causar um novo erro de compilação:


 Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option * https://stackoverflow.com/questions/48988778/cannot-inline-bytecode-built-with-jvm-target-1-8-into-bytecode-that-is-being-bui 

Para resolver, é necessário registrar explicitamente a versão da JVM para o Kotlin:


 android { … kotlinOptions { jvmTarget = "1.8" } } 

Isso parece ser tudo. Muito mais fácil do que construir seu próprio estúdio)


Vamos tentar executar o Hello World (também do artigo de Cyril, mas, ao contrário dele, adicione Compose dentro do Fragment). O layout do fragmento é um FrameLayout vazio.


 override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val fragmentView = inflater.inflate(R.layout.fragment_about, container, false) (fragmentView as ViewGroup).setContent { Hello("Jetpack Compose") } return fragmentView } @Composable fun Hello(name: String) = MaterialTheme { FlexColumn { inflexible { // Item height will be equal content height TopAppBar<MenuItem>( // App Bar with title title = { Text("Jetpack Compose Sample") } ) } expanded(1F) { // occupy whole empty space in the Column Center { // Center content Text("Hello $name!") // Text label } } } } 

Começamos, a seguinte tela é exibida:


imagem

Devido ao fato de o Composable usar o tema Material padrão, temos uma AppBar roxa. Bem, e, como esperado, não é de todo consistente com o tema sombrio do aplicativo:


imagem

Vamos tentar resolver isso.


Temas e estilos. Integração com os existentes no projeto.


Para usar estilos existentes no Composable, passamos-os para o construtor MaterialTheme:


 @Composable fun Hello(name: String) = MaterialTheme(colors = MaterialColors( primary = resolveColor(context, R.attr.colorPrimary, MaterialColors().primary), secondary = resolveColor(context, R.attr.colorSecondary, MaterialColors().secondary), onBackground = resolveColor(context, R.attr.textColor, MaterialColors().onBackground) )){...} 

O MaterialTheme em si consiste em duas partes: MaterialColors e MaterialTypography.
Para resolver as cores, usei um invólucro sobre os estilos:


 private fun resolveColor(context: Context?, @AttrRes attrRes: Int, colorDefault: Color) = context?.let { Color(resolveThemeAttr(it, attrRes).data.toLong()) } ?: colorDefault private fun resolveThemeAttr(context: Context, @AttrRes attrRes: Int): TypedValue { val theme = context.theme val typedValue = TypedValue() theme.resolveAttribute(attrRes, typedValue, true) return typedValue } 

Nesta fase, o AppBar ficará verde. Mas para repintar o texto, você precisa executar mais uma ação:


 Text("Hello $name!", style = TextStyle(color = +themeColor { onBackground })) 

O tema é aplicado ao widget usando a operação unary plus. Ainda o veremos ao trabalhar com o Estado.


Agora, a nova tela parece uniforme com o restante do aplicativo nas duas variantes do tema:


imagem

O Compose também encontrou o arquivo DarkTheme.kt nas fontes, cujas funções podem ser usadas para determinar vários gatilhos para ativar um tema sombrio no Android P e 10.


Testes de acessibilidade e interface do usuário.


Até que a tela comece a crescer com novos elementos, vamos ver como fica no Inspetor de Layout e com a exibição das bordas dos elementos no Modo de Desenvolvimento ativada:



imagem

Aqui veremos o FrameLayout, dentro do qual apenas o AndroidComposeView. As ferramentas existentes para testes de acessibilidade e interface do usuário não são mais aplicáveis? Talvez, em vez deles, agora exista uma nova biblioteca: androidx.ui:ui-test .


Os principais componentes e análogos dos herdeiros da View.


Agora vamos tentar tornar a tela um pouco mais informativa. Primeiro, altere o texto, adicione um botão que leva à página do aplicativo no Google Play e uma imagem com um logotipo. Vou mostrar o código imediatamente e o que aconteceu:


 @Composable fun AboutScreen() = MaterialTheme(...) { FlexColumn { inflexible { TopAppBar<MenuItem>(title = { Text(getString(R.string.about)) }) } expanded(1F) { VerticalScroller { Column { Image() Title() MyButton() } } } } } private fun Image() { Center { Padding(16.dp) { Container( constraints = DpConstraints( minWidth = 96.dp, minHeight = 96.dp ) ) { imageFromResource(resources, R.drawable.ic_launcher) } } } } private fun Title() { Center { Padding(16.dp) { Text(getString(R.string.app_name) + " " + BuildConfig.VERSION_NAME, style = TextStyle(color = +themeColor { onBackground })) } } } private fun MyButton() { Center { Padding(16.dp) { Button(getString(R.string.about_button), onClick = { openAppInPlayStore() }) } } } 

imagem

Os princípios básicos da composição do widget não foram alterados desde a primeira aparição das fontes Compose .


Do interessante:


  • As funções para exibir elementos individuais não precisam ser anotadas com @Composable.
  • Quase todas as propriedades dos widgets se transformaram em widgets separados (Center em vez de android: gravidade, Padding em vez de android: margin, ...)
  • Não consegui exibir a imagem em drawables.
  • O parâmetro onClick do botão não é o último, pelo que é impossível transmiti-lo como um lambda sem um nome explícito, o que pareceria mais lógico:
     Button(“Text"){ openAppInPlayStore() } 

Agora, vamos examinar o principal grupo de visualizações existente e tentar encontrar análogos no Compose.


Em vez de FrameLayout, você pode usar Stack. Tudo é simples aqui: os widgets filhos se sobrepõem e são posicionados dependendo da função usada para o anexo: alinhados, posicionados ou expandidos.


LinearLayout é substituído imediatamente por dois widgets: Coluna e Linha em vez de usar o parâmetro android: guidance. Eles, por sua vez, contêm FlexColumn e FlexRow com uma camada de função inflexível sobre uma subárvore aninhada. Bem, o FlexColumn e o FlexRow são criados no Flex com a orientation = LayoutOrientation.Vertical do parâmetro orientation = LayoutOrientation.Vertical ou Horizontal .


Uma hierarquia semelhante para os widgets FlowColumn, FlowRow e Flow. A principal diferença: se o conteúdo não couber em uma coluna ou linha, a próxima será desenhada a seguir e os widgets incorporados "fluirão" para lá. É difícil para mim imaginar o real objetivo desses widgets.


O efeito ScrollView é conseguido colocando uma coluna ou linha dentro de um VerticalScroller ou HorizontalScroller. Os dois compõem dentro do Scroller, passando o parâmetro isVertical = true ou false .


Em busca de um análogo para ConstraintLayout, ou pelo menos RelativeLayout, deparei com um novo widget de Tabela. Tentei executar o código de exemplo no meu aplicativo: DataTableSamples.kt . Mas, como não tentei simplificar o exemplo, não funcionou para fazê-lo funcionar.


imagem

Trabalhar com o Estado


Uma das inovações mais esperadas da estrutura é sua prontidão para uso em arquiteturas unidirecionais construídas com base em um único estado. E isso deveria introduzir a anotação @Model para marcar classes que fornecem Estado para renderizar a interface do usuário.
Considere um exemplo:


 data class DialogVisibleModel(val visible: Boolean, val dismissPushed: Boolean = false) ... @Composable fun SideBySideAlertDialogSample() { val openDialog = +state { DialogVisibleModel(true) } Button(text = "Ok", onClick = { openDialog.value = DialogVisibleModel(true) }) if (openDialog.value.visible) { AlertDialog( onCloseRequest = { // Because we are not setting openDialog.value to false here, // the user can close this dialog only via one of the buttons we provide. }, title = { Text(text = "Title") }, text = { Text("This area typically contains the supportive text" + " which presents the details regarding the Dialog's purpose.") }, confirmButton = { Button("Confirm", onClick = { openDialog.value = DialogVisibleModel(false) }) }, dismissButton = { if (!openDialog.value.dismissPushed) Button("Dismiss", onClick = { openDialog.value = DialogVisibleModel(true, true) }) else { //hidden } }, buttonLayout = AlertDialogButtonLayout.SideBySide ) } } 

Isso cria uma classe de dados para o modelo de estado e não precisa ser marcada com a anotação @Model.
O próprio estado inicial é criado dentro da função @Composable usando o estado +.
A visibilidade da caixa de diálogo é determinada pela propriedade visible do modelo obtida chamando a propriedade value.
Essa propriedade também pode ser configurada para um novo objeto imutável, como acontece no onClick dos dois botões. O primeiro se esconde, o segundo - fecha o diálogo. A caixa de diálogo pode ser reaberta clicando no botão Ok, definido dentro da mesma função @Composable.
Ao tentar estabelecer um estado fora desta função, ocorre um erro:
java.lang.IllegalStateException: Composition requires an active composition context.
O contexto pode ser obtido atribuindo o valor da função setContent {} no onCreateView, mas ainda não é claro como usá-lo, por exemplo, no Presenter ou em outra classe que não seja Fragmento ou Atividade, para alterar o estado.


imagem

Isso conclui nossa análise da nova biblioteca Jetpack Compose. A estrutura justifica seu nome arquitetonicamente, substituindo toda a herança, que era tão inconveniente na hierarquia Exibir, pela composição. Ainda há muitas perguntas sobre como os análogos de ViewGroups mais complexos serão implementados, como ConstraintLayout e RecyclerView; documentação e visualizações insuficientes.


É absolutamente claro que o Compose não está pronto para uso, mesmo em pequenas aplicações de combate.


Mas esta é apenas a primeira versão do Dev Preview. Será interessante observar o desenvolvimento do conceito de trabalhar com o Estado e as bibliotecas da comunidade com base no Compose.


Se você encontrar exemplos mais bem-sucedidos de código ou documentação para casos que não consegui, escreva nos comentários.

Source: https://habr.com/ru/post/pt471670/


All Articles