Criando a Estrutura Base Das Telas Com Lista - YouTuber Android App - Parte 4

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes! Você receberá um email de confirmação. Somente depois de confirma-lo é que eu poderei lhe enviar os conteúdos semanais exclusivos. Os artigos em PDF são entregues somente para os inscritos na lista.

Email inválido.
Blog /Android /Criando a Estrutura Base Das Telas Com Lista - YouTuber Android App - Parte 4

Criando a Estrutura Base Das Telas Com Lista - YouTuber Android App - Parte 4

Vinícius Thiengo
(2581) (4)
Go-ahead
"O método consciente de tentativa e erro é mais bem-sucedido que o planejamento de um gênio isolado."
Peter Skillman
Prototipagem Android
Capa do curso Prototipagem Profissional de Aplicativos
TítuloAndroid: Prototipagem Profissional de Aplicativos
CategoriasAndroid, Design, Protótipo
AutorVinícius Thiengo
Vídeo aulas186
Tempo15 horas
ExercíciosSim
CertificadoSim
Acessar Curso
Quer aprender a programar para Android? Acesse abaixo o curso gratuito no Blog.
Lendo
TítuloDomain-driven Design Destilado
CategoriaEngenharia de Software
Autor(es)Vaughn Vernon
EditoraAlta Books
Edição
Ano2024
Páginas160
Conteúdo Exclusivo
Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba gratuitamente conteúdos Android sem precedentes!
Email inválido

Tudo bem?

Neste artigo vamos dar continuidade ao nosso projeto de aplicativo Android para YouTubers.

Nesta parte quatro do projeto nós vamos desenvolver toda a estrutura comum entre os fragmentos, telas, que têm como parte do conteúdo o framework de lista RecyclerView.

Animação do app Android com as telas que têm um framework de lista

Na verdade tem uma tela que pouco vai usufruir de toda está estrutura comum que iremos desenvolver aqui. No decorrer do conteúdo eu falo qual é essa tela e o porquê disso.

Antes de prosseguir, saiba que:

A versão e-book (PDF 📙) do projeto completo está disponível somente para os inscritos da lista de e-mails 📧 do Blog.

Se você se inscrever ainda hoje é possível que o link do e-book esteja disponível na página de confirmação de sua inscrição na lista de e-mails do Blog.

É isso.

A seguir os tópicos que estaremos abordando neste quarto conteúdo do projeto de aplicativo Android para YouTubers:

O que já temos até aqui

Se você chegou no projeto somente agora, saiba que este não é o primeiro e nem mesmo o último artigo já publicado.

Todo o roteiro de construção do projeto está na listagem a seguir:

Para tirar o máximo proveito do projeto de aplicativo Android que estaremos desenvolvendo é inteligente seguir cada um dos conteúdos na ordem apresentada na lista anterior.

Repositório

Para ter acesso a todos os códigos fontes do projeto já finalizado, entre no repositório GitHub dele em:

➙ GitHub do projeto de aplicativo Android para YouTuber.

Fragmento de lista

Se observarmos bem o protótipo estático será possível ver que as telas que têm lista de itens têm também a mesma estrutura de layout:

Algumas telas do app Android que apresentam as mesmas características de layout

Sendo assim é possível construir toda uma configuração comum a essas telas.

Digo, quase todas elas. Pois o fragmento de PlayLists, como trabalha também com dados remotos...

... este fragmento em nosso primeiro release terá a maior parte do código dele ainda dentro da própria classe.

É aquilo:

O que for óbvio para evitar a repetição de código, então vamos aplicar.

É isso. Sem muito rodeio, vamos partir para os códigos. Começando pelos mais simples.

Estáticos de interface

Como já é comum em todo o desenvolvimento deste projeto, vamos iniciar pela parte fácil, os arquivos estáticos, XML.

Cores

No arquivo de cores /res/values/colors.xml adicione as novas definições a seguir:

...
<!-- Content -->
<color name="colorContentTitle">#666666</color>
<color name="colorContentText">#444444</color>
<color name="colorContentLink">#444444</color>

<!-- List item -->
<color name="colorListItemBackground_RippleStart">#F6F6F6</color>
<color name="colorListItemBackground_RippleEnd">#DDDDDD</color>
<color name="colorListItemStroke">#EEEEEE</color>
<color name="colorListItemText">#444444</color>
<color name="colorListItemNoDataIcon">#BBBBBB</color>
...

Background de item

Como os itens de lista são clicáveis, vamos adicionar um efeito ripple neles junto à definição de background.

Definição que contém uma cor específica (<solid>), um tipo de borda (<stroke>) e um grau específico de curvatura nas bordas (<corners>).

Item de lista de telas com framework de lista

Sendo assim, no folder /res/drawable, adicione o arquivo bg_item_list.xml exatamente com o código a seguir:

<?xml version="1.0" encoding="utf-8"?>
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/colorListItemBackground_RippleEnd">

<item>
<shape android:shape="rectangle">

<solid android:color="@color/colorListItemBackground_RippleStart" />

<corners android:radius="4dp" />

<stroke
android:width="1dp"
android:color="@color/colorListItemStroke" />
</shape>
</item>
</ripple>

 

Com o arquivo anterior configurado como background de item nós conseguiremos o seguinte efeito:

Animação de um item de lista sendo clicado (Ripple Efect)

Novos estilos

No arquivo de estilos, /res/values/styles.xml, adicione os seguintes novos temas:

...
<style name="AppTheme.Title">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginStart">@dimen/standard_screen_side_margin</item>
<item name="android:layout_marginEnd">@dimen/standard_screen_side_margin</item>
<item name="android:layout_marginBottom">10dp</item>
<item name="android:fontFamily">@font/quicksand_variable_font_wght</item>
<item name="android:textSize">18sp</item>
<item name="android:textColor">@color/colorContentTitle</item>
</style>

<style
name="AppTheme.TextContent"
parent="AppTheme.Title">

<item name="android:fontFamily">sans-serif</item>
<item name="android:textSize">14sp</item>
<item name="android:autoLink">all</item>
<item name="android:linksClickable">true</item>
<item name="android:textColorLink">@color/colorContentLink</item>
<item name="android:textColor">@color/colorContentText</item>
</style>

<style name="AppTheme.ListItemContainer">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginStart">@dimen/standard_screen_side_margin</item>
<item name="android:layout_marginEnd">@dimen/standard_screen_side_margin</item>
<item name="android:layout_marginTop">5dp</item>
<item name="android:layout_marginBottom">5dp</item>
<item name="android:background">@drawable/bg_item_list</item>
<item name="android:padding">8dp</item>
</style>

<style name="AppTheme.ListItemIcon">
<item name="android:layout_width">34dp</item>
<item name="android:layout_height">34dp</item>
<item name="android:layout_centerVertical">true</item>
<item name="android:layout_marginEnd">12dp</item>
</style>

<style name="AppTheme.ListItemMainText">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_alignParentTop">true</item>
<item name="android:layout_alignParentEnd">true</item>
<item name="android:layout_marginTop">8dp</item>
<item name="android:layout_marginBottom">8dp</item>
<item name="android:textColor">@color/colorListItemText</item>
<item name="android:gravity">center_vertical</item>
</style>

<style
name="AppTheme.ListItemAuxText"
parent="AppTheme.ListItemMainText">

<item name="android:layout_alignParentTop">false</item>
<item name="android:layout_marginTop">0dp</item>
<item name="android:textSize">12sp</item>
<item name="android:textStyle">bold</item>
</style>
...

 

Apesar de aparentar ser muita coisa, com os novos estilos definidos anteriormente nós estamos reduzindo consideravelmente a quantidade de código em arquivos de layout.

Note que um tema ancestral, parent, somente foi utilizado nos novos estilos quando o contexto era o mesmo e as configurações eram similares.

Nós poderíamos utilizar parent na condição onde AppTheme.TextContent seria um tema ancestral de AppTheme.ListItemContainer, mas os contextos são completamente diferentes:

  • Um, AppTheme.TextContent, é para definições de estilo em texto;
  • o Outro, AppTheme.ListItemContainer, é para definição de estilo no ViewGroup container da UI de item de lista.

Neste caso não faz sentido o reaproveitamento de código de estilo.

Layout de item

O layout de item é bem simples e contém alguns componentes visuais que terão a visibilidade em item trabalhada em tempo de execução na classe ViewHolder do framework de lista desse fragmento "genérico".

A estrutura do layout de item é a seguinte:

Diagrama do layout list_item.xml

No folder /res/layout adicione o arquivo XML list_item.xml com o código a seguir:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/AppTheme.ListItemContainer">

<ImageView
android:id="@+id/iv_icon"
style="@style/AppTheme.ListItemIcon" />

<TextView
android:id="@+id/tv_main_text"
style="@style/AppTheme.ListItemMainText"
android:layout_marginTop="8dp"
android:layout_toEndOf="@+id/iv_icon" />

<TextView
android:id="@+id/tv_first_aux_text"
style="@style/AppTheme.ListItemAuxText"
android:layout_below="@+id/tv_main_text"
android:layout_marginBottom="0dp"
android:layout_toEndOf="@+id/iv_icon" />

<TextView
android:id="@+id/tv_second_aux_text"
style="@style/AppTheme.ListItemAuxText"
android:layout_below="@+id/tv_first_aux_text"
android:layout_toEndOf="@+id/iv_icon" />
</RelativeLayout>

 

Se você estiver apenas iniciando no desenvolvimento de aplicativos Android, é possível que você se sinta confuso quando além do estilo definido em componente visual, tenha também definições de estilo diretamente na estrutura XML no componente.

Como no TextView a seguir (retirado do layout anterior) com a definição de layout_marginBottom:

...
<TextView
android:id="@+id/tv_first_aux_text"
style="@style/AppTheme.ListItemAuxText"
android:layout_below="@+id/tv_main_text"
android:layout_marginBottom="0dp"
android:layout_toEndOf="@+id/iv_icon" />
...

 

Parte da ideia de um novo tema definido em styles.xml é reaproveitar o máximo de código estático possível.

Porém alguns componentes ainda terão que ter as suas próprias definições diretamente na estrutura de código.

Então se acostume, caso esteja iniciando no desenvolvimento Android, com componentes visuais que fazem o uso do atributo style e também contêm definições próprias de estilo.

Agora vamos ao último arquivo estático deste fragmento.

Layout de fragmento

O layout principal desse fragmento comum é ainda mais simples do que layout de item.

Veja a estrutura dele:

Diagrama do layout fragment_framework_list.xml

Sendo assim, no folder /res/layout, crie o arquivo fragment_framework_list.xml com o código a seguir:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<TextView
android:id="@+id/tv_main_text"
style="@style/AppTheme.Title" />

<TextView
android:id="@+id/tv_sub_title"
style="@style/AppTheme.TextContent"
android:visibility="gone" />

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_items"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>

 

Simples, certo?

Com isso podemos ir com segurança aos códigos dinâmicos desta parte do projeto.

Domínio

A entidade de domínio é uma interface que será comum às classes de domínio específicas de cada fragmento que contém uma lista de itens.

Sendo assim, dentro do pacote /model, crie a Interface ListItem com o exato código a seguir:

package thiengo.com.br.canalvinciusthiengo.model

import android.content.res.Resources
import android.net.Uri

/**
* Interface utilizada como "estrutura comum" a todos
* os itens de lista presentes em projeto.
*
* Assim é possível economizar na quantidade de código
* que seria necessária para inúmeros adapters e
* ViewHolders de cada [Fragment] que tem um [RecyclerView]
* (framework de lista).
*/
interface ListItem {

/**
* Retorna o principal texto em item de lista, o
* que tem destaque e maior tamanho de fonte.
*
* @return o texto principal do item de lista.
*/
fun getMainText() : String

/**
* Retorna o possível primeiro texto auxiliar do
* item de lista. Texto com informações extras,
* porém ainda necessárias.
*
* @return o texto primário auxiliar do item de
* lista.
*/
fun getFirstAuxText() : String
= ""

/**
* Retorna o possível segundo texto auxiliar do
* item de lista. Texto com informações extras,
* porém ainda necessárias.
*
* @param resources objeto para acesso
* a recursos do sistema.
* @return o texto secundário auxiliar do
* item de lista.
*/
fun getSecondAuxText( resource: Resources ) : String
= ""

/**
* Retorna a Web URI que deve ser acionada
* junto a um objeto [Intent] em uma invocação
* de startActivity().
*
* Isso para abrir o aplicativo responsável
* pelo conteúdo completo informado em item de
* lista.
*
* @return a Web URI do item acionado.
*/
fun getWebUri() : Uri?
= null

/**
* Retorna a app URI que deve ser acionada
* junto a um objeto [Intent] em uma invocação
* de startActivity().
*
* Isso para abrir o aplicativo responsável
* pelo conteúdo completo informado em item de
* lista.
*
* @return a app URI do item acionado.
*/
fun getAppUri() : Uri?
= null

/**
* Retorna o identificador único do ícone que
* melhor representa o item de lista.
*
* O ícone deve estar local no app, como recurso
* do aplicativo (/res/drawable).
*
* @return o identificador único do ícone.
*/
fun getIcon() : Int
}

 

Os únicos métodos que não têm corpo já definido em Interface e que devem ser implementados pelas classes que implementam essa entidade, são:

  • getMainText();
  • getIcon().

Isso, pois ao menos os dados retornados por esses dois métodos sempre deverão estar em item de lista.

Note que na definição de getSecondAuxText() temos como parâmetro um objeto do tipo Resources:

...
fun getSecondAuxText( resource: Resources ) : String
= ""
...

 

Isso, pois em nossa versão de projeto somente um fragmento com framework de lista vai precisar desse segundo texto auxiliar em tela.

Esse fragmento precisará do objeto do tipo Resources, pois parte do texto retornado pode ser traduzido em versões de internacionalização de strings.xml.

Sendo assim, sabendo que todo texto local que pode ser traduzido têm que ir para strings.xml, é preciso acessar essa parte do texto via resources.getString().

Mas confesso que em possíveis futuros releases deste projeto eu colocaria esse objeto do tipo Resources como propriedade da Interface ListItem.

Pois assim se mais métodos de texto precisassem de getString() o objeto estaria disponível para todos.

⚠ Importante:

Quase todos os códigos do projeto têm comentários.

É MUITO IMPORTANTE que você leia também todos os comentários dos códigos para literalmente entender o que está sendo realizado.

Lembrando que estamos desenvolvendo um aplicativo Android profissional completo e assim ele poderá ser ao menos um novo projeto em seu portfólio como desenvolvedor mobile.

ViewHolder

A classe que implementa o padrão ViewHolder é ainda simples, porém com alguns blocos condicionais para saber quais componentes visuais de texto devem ou não estar em tela.

No pacote /ui/adapter crie a classe ListItemViewHolder com o código a seguir:

package thiengo.com.br.canalvinciusthiengo.ui.adapter

import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import thiengo.com.br.canalvinciusthiengo.R
import thiengo.com.br.canalvinciusthiengo.model.ListItem

/**
* Classe responsável por aplicar o padrão
* ViewHolder nas listas de itens dos fragmentos
* que contêm o framework [RecyclerView].
*
* @property adapter adaptador de itens de lista.
* @property callExternalAppCallback contém o
* algoritmo de execução quando um novo item é
* acionado pelo usuário.
* @property itemView layout de item.
* @constructor cria um objeto completo do tipo
* [ListItemViewHolder].
*/
class ListItemViewHolder(
private val adapter: ListItemAdapter,
private val callExternalAppCallback: (ListItem)->Unit,
itemView: View
) : RecyclerView.ViewHolder( itemView ), View.OnClickListener {

/**
* Propriedades de layout, UI.
*/
private var ivIcon: ImageView
private var tvMainText: TextView
private var tvFirstAuxText: TextView
private var tvSecondAuxText: TextView

/**
* Bloco de inicialização da UI do layout
* de item de lista.
*/
init {
itemView.setOnClickListener( this )

ivIcon = itemView.findViewById( R.id.iv_icon )
tvMainText = itemView.findViewById( R.id.tv_main_text )
tvFirstAuxText = itemView.findViewById( R.id.tv_first_aux_text )
tvSecondAuxText = itemView.findViewById( R.id.tv_second_aux_text )
}

/**
* Define em UI os dados que devem ser
* apresentados em item de lista.
*
* @param item item de lista.
*/
fun setModel( item: ListItem ) {
ivIcon.setImageResource( item.getIcon() )
ivIcon.contentDescription = item.getMainText()

tvMainText.text = item.getMainText()
setAuxTexts( item = item )
}

/**
* Define em UI quais textos auxiliares
* de item de lista devem ou não aparecer
* em layout.
*
* @param item item de lista.
*/
private fun setAuxTexts( item: ListItem ){

if( item.getFirstAuxText().isNotEmpty() ){

tvFirstAuxText.text = item.getFirstAuxText()
tvFirstAuxText.visibility = View.VISIBLE

val secondText = item.getSecondAuxText(
resource = adapter.context.resources
)

if( secondText.isNotEmpty() ){
tvSecondAuxText.text = secondText
tvSecondAuxText.visibility = View.VISIBLE
}
else{
tvSecondAuxText.visibility = View.GONE
}
}
else{
tvFirstAuxText.visibility = View.GONE
tvSecondAuxText.visibility = View.GONE
}
}

override fun onClick( view: View ) {
callExternalAppCallback(
adapter.items[ adapterPosition ]
)
}
}

 

A lógica de negócio no método setAuxTexts() pode parecer confusa em relação ao TextView tvSecondAuxText. Principalmente a lógica do último bloco else:

...
if( item.getFirstAuxText().isNotEmpty() ){
...
}
else{
tvFirstAuxText.visibility = View.GONE
tvSecondAuxText.visibility = View.GONE
}
...

 

Mas basicamente a lógica para este componente visual é:

Se o primeiro texto auxiliar (tvFirstAuxText) não for ficar disponível em tela (View.GONE), então o segundo (tvSecondAuxText) nem mesmo precisa ser testado, pois ele também não ficará.

Ou seja, a pré-condição para o segundo texto auxiliar ser apresentado em tela é que o primeiro texto auxiliar também esteja disponível em tela.

Adapter

Ainda mais simples do que o ViewHolder é a classe adaptadora desse fragmento comum.

No pacote /ui/adapter crie a classe ListItemAdapter com o código a seguir:

package thiengo.com.br.canalvinciusthiengo.ui.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import thiengo.com.br.canalvinciusthiengo.R
import thiengo.com.br.canalvinciusthiengo.model.ListItem

/**
* Classe adaptadora de itens com lógica comum a
* todos os fragmentos do app que têm como parte
* do conteúdo o framework de lista [RecyclerView].
*
* Define qual ViewHolder, qual lista de dados e
* qual layout será utilizado no framework de
* lista do fragmento.
*
* @property context contexto do aplicativo.
* @property items itens de lista do tipo (que
* implementem) [ListItem].
* @property callExternalAppCallback contém o
* algoritmo de execução quando um item é
* acionado pelo usuário.
* @constructor cria um objeto completo do tipo
* [ListItemAdapter].
*/
class ListItemAdapter(
val context: Context,
val items: List<ListItem>,
private val callExternalAppCallback: (ListItem)->Unit
) : RecyclerView.Adapter<ListItemViewHolder>() {

override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int ) : ListItemViewHolder {

val layout = LayoutInflater
.from( context )
.inflate(
R.layout.list_item,
parent,
false
)

return ListItemViewHolder(
adapter = this,
callExternalAppCallback = callExternalAppCallback,
itemView = layout
)
}

override fun onBindViewHolder(
holder: ListItemViewHolder,
position: Int ){

holder.setModel(
item = items[ position ]
)
}

override fun getItemCount()
= items.size
}

 

Como em outros trechos deste nosso projeto de aplicativo: quase todo o código é boilerplate.

Enfim vamos ao trecho de código final deste fragmento comum a "todos" os fragmentos com framework de lista em layout.

FrameworkListFragment

Primeiro, no pacote /ui crie um novo pacote com o rótulo /fragment.

Antes de colocar todo o código da classe abstrata FrameworkListFragment, saiba que está é uma das classes críticas em projeto.

Ou seja, caso você tenha em mente adicionar ainda mais funcionalidades aos fragmentos que contém framework de lista em layout, certamente essa será a classe mais trabalhada.

Logo, domine essa classe para que você não cometa erros quando precisar evoluí-la.

Pois um erro nessa classe, principalmente se for erro de lógica, vai atrapalhar o correto funcionamento de todos os fragmentos filhos dela.

Sendo assim, no novo pacote /ui/fragment crie a classe FrameworkListFragment como a seguir:

package thiengo.com.br.canalvinciusthiengo.ui.fragment

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.fragment_framework_list.*
import thiengo.com.br.canalvinciusthiengo.R

/**
* Classe base para todos os fragmentos do projeto
* que têm como parte principal do layout o framework
* de lista [RecyclerView] e os dados estão somente
* local em projeto (/data/fixed).
*/
abstract class FrameworkListFragment : Fragment() {

companion object {
/**
* Constante com o valor padrão em DPs que
* deve ser aplicado na bottomMargin do
* título principal do fragmento em relação
* ao sub-título, isso somente se existir
* algum sub-título.
*/
private const val MARGIN_TO_SUB_TITLE = 5
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? ): View? {

return inflater.inflate(
R.layout.fragment_framework_list,
container,
false
)
}

/**
* Configuração dos componentes de UI extra
* framework de lista e que estão no mesmo
* layout de fragmentos com lista de itens.
*
* @param titleText título principal em tela.
* @param subTitleText sub-título (descrição
* mais detalhada do conteúdo).
*/
protected fun setUiModel(
titleText: String,
subTitleText: String = "" ){

tv_main_text.text = titleText

tv_sub_title.text = subTitleText
tv_sub_title.visibility = if( subTitleText.isEmpty() ){
View.GONE
}
else{
updateMainTextMargin()
View.VISIBLE
}
}

/**
* Atualiza o valor de bottomMargin do título do
* fragmento quando também há em layout um outro
* [TextView] de conteúdo extra além do [TextView]
* de título e do [RecyclerView] de itens de lista.
*
* Desta forma o conteúdo fica mais apresentável.
*
* O valor em bottomMargin precisa ser em pixels,
* por isso a necessidade de conversão de DPs para
* pixels.
*/
private fun updateMainTextMargin(){
val tvTitleLayoutParams = (tv_main_text.layoutParams as LinearLayout.LayoutParams)

tvTitleLayoutParams.bottomMargin = convertDpToPixels()
}

/**
* Converte um número inteiro [MARGIN_TO_SUB_TITLE]
* em DPs para a sua versão em pixels.
*
* @return valor em pixels do valor em DP informado
* em [MARGIN_TO_SUB_TITLE].
*/
private fun convertDpToPixels() : Int {
val scale = resources.displayMetrics.density

return (MARGIN_TO_SUB_TITLE * scale + 0.5f).toInt()
}

/**
* Permite que todos os fragmentos filhos utilizem um
* mesmo código de inicialização de framework de lista.
*
* @param adapter adaptador de item de lista
* adequadamente configurado de acordo com o fragmento
* que o utiliza.
*/
protected fun <T : RecyclerView.ViewHolder> initList(
adapter: RecyclerView.Adapter<T> ){

val layoutManager = LinearLayoutManager( activity )
rv_items.layoutManager = layoutManager

rv_items.setHasFixedSize( true )
rv_items.adapter = adapter
}

/**
* Invoca um aplicativo auxiliar para que o usuário
* tenha acesso a outros conteúdos do canal que estão
* disponíveis em outras redes (apps ou sites).
*
* Caso os dados de URI fornecidos sejam inválidos,
* então uma mensagem de falha é apresentada.
*
* @param webUri URL para abertura de app auxiliar
* ou navegador Web.
* @param appUri endereço para abertura de app nativo
* auxiliar.
* @param failMessage mensagem de falha caso as URIs
* sejam inválidas.
*/
protected fun callExternalApp(
webUri: Uri? = null,
appUri: Uri? = null,
failMessage: String ){

val intentApp = getIntentUri( uri = appUri )
val intentWeb = getIntentUri( uri = webUri )
var intent: Intent? = null

/**
* Caso não tenha no aparelho o aplicativo oficial
* da rede acionada, então cria a configuração
* para tentar abrir a rede pelo navegador Web
* padrão do device.
*/
if( intentApp != null
&& intentApp.resolveActivity( activity!!.packageManager ) != null ){

intent = intentApp
}
else if( intentWeb != null
&& intentWeb.resolveActivity( activity!!.packageManager ) != null ){

intent = intentWeb
}

if( intent != null ){
activity!!.startActivity( intent )
}
else{
/**
* É utópico, mas pode ocorrer de não haver
* nem mesmo um navegador Web no aparelho do
* usuário que abra a URL (se houver algo em
* [webUri]) da rede acionada.
*
* Sendo assim, ao invés de gerar uma exceção,
* nós avisamos ao usuário a necessidade de
* instalar o aplicativo adequado.
*/
Toast
.makeText(
activity,
failMessage,
Toast.LENGTH_LONG
)
.show()
}
}

/**
* Retorna um objeto [Intent] válido ou null de acordo
* com o valor presente em uri.
*
* @param uri endereço para abertura de app auxiliar.
* @return intent com configuração de abertura de
* app auxiliar.
*/
private fun getIntentUri( uri: Uri? ) : Intent?
= if( uri != null ){
Intent(
Intent.ACTION_VIEW,
uri
)
}
else{
null
}
}

 

Pode acontecer uma confusão em relação a todo o algoritmo que atualiza a bottomMargin do componente visual de título do fragmento. Algoritmo que se inicia em updateMainTextMargin().

E a resposta que temos que ter em relação à essa confusão é:

Estamos dando valor a todos os detalhes definidos em protótipo estático.

E o uso de um algoritmo dinâmico para a entrega do detalhe de margem de fundo (dado estático de layout) foi a melhor opção neste primeiro release de projeto.

Done.

Ok, Thiengo. Entendido. Mas por que você utiliza chaves ({}) em blocos condicionais como o a seguir?

...
if( intentApp != null
&& intentApp.resolveActivity( activity!!.packageManager ) != null ){

intent = intentApp
}
else if( intentWeb != null
&& intentWeb.resolveActivity( activity!!.packageManager ) != null ){

intent = intentWeb
}
...

 

Não seria melhor a versão sem chaves, com menos código, como o seguinte?

...
if( intentApp != null && intentApp.resolveActivity( activity!!.packageManager ) != null )
intent = intentApp
else if( intentWeb != null && intentWeb.resolveActivity( activity!!.packageManager ) != null )
intent = intentWeb
...

 

Depende!

Primeiro é importante ter em mente que o "uso" ou "não uso" de chaves ({}), quando temos essas duas opções...

... isso não afeta em nada a performance de processamento do projeto.

O uso contínuo de chaves em blocos condicionais mesmo quando é possível omiti-los é, nada mais nada menos, que uma característica de codificação minha... eu sempre coloco as chaves 😁.

É isso.

E...

... se você já está enxergando códigos que podem ser melhorados, encapsulados, ... e ao mesmo tempo você já planeja monetizar este projeto de aplicativo Android assim que termina-lo.

Então minha dica é:

Não mexa no código agora. Isso é um primeiro release e é normal encontrar possíveis melhorias.

Foque somente em entender e terminar o projeto e então em gerar a primeira "nota fiscal" com ele pronto.

Period.

Até este ponto do projeto temos a seguinte configuração física (das entidades de código dinâmico) em IDE:

Configuração física do projeto Android

Próximo conteúdo

Está importante parte do projeto foi finalizada com sucesso.

No próximo conteúdo nós vamos, passo a passo, aos fragmentos que herdam de FrameworkListFragment e também a um fragmento simples de About Channel.

Segue o link para acesso ao próximo conteúdo:

➙ Construindo os Fragmentos de Conteúdo Local - Parte 5.

Então é isso.

Relaxe a cabeça um pouco. Tome um café ☕ 🧀 🎧. E...

... te vejo na Parte 5 do projeto.

Se houverem dúvidas ou dicas deste quarto conteúdo do aplicativo, então deixe nos comentários que logo eu lhe respondo.

Não esqueça de conhecer também o meu canal no YouTube (caso você ainda não conheça) e...

... não deixe de se inscrever na 📩 lista de e-mails para também garantir a versão em PDF não somente deste projeto de aplicativo Android, mas também de cada novo "conteúdo mini-curso".

Abraço.

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes!
Email inválido

Relacionado

Colocando Telas de Introdução em Seu Aplicativo AndroidColocando Telas de Introdução em Seu Aplicativo AndroidAndroid
Chips Android, Quando e Como UtilizarChips Android, Quando e Como UtilizarAndroid
True Time API Para Data e Horário NTP no AndroidTrue Time API Para Data e Horário NTP no AndroidAndroid
Porque e Como Utilizar Vetores no AndroidPorque e Como Utilizar Vetores no AndroidAndroid

Compartilhar

Comentários Facebook

Comentários Blog (4)

Para código / script, coloque entre [code] e [/code] para receber marcação especifica.
Forneça seu nome válido.
Forneça seu email válido.
Forneça o comentário.
Enviando, aguarde...
Robson Tomás Paulo (1) (1)
20/09/2020
Olá Thiengo tem uma sintaxe que não domino. o que significa colocar uma View antes do nome da função como em:
fun  initList(
        adapter: RecyclerView.Adapter )
Responder
Vinícius Thiengo (1) (1)
21/09/2020
Robson, tudo bem?

Essa sintaxe na verdade representa uma área de estudo da programação de software denominada "Covariância".

Área que está dentro de outra área, "Tipos genéricos".

Basicamente a assinatura de initList() diz:

"O método initList() somente pode ser invocado com algum argumento do tipo RecyclerView.Adapter onde o parâmetro genérico (T) de RecyclerView.Adapter deve ser um subtipo (herda ou implementa) de RecyclerView.ViewHolder".

Robson, lembrando que todo tipo é também subtipo dele mesmo.

Ou seja, se T for exatamente do tipo RecyclerView.ViewHolder, então ele também é aceito como argumento de initList().

Resumidamente é isso.

Se você estiver com um tempo legal, dê uma estudada em Genéricos e em Covariância e Contravariância.

São para mim os assuntos mais pesados da área de desenvolvimento de software.

Mas são recursos de linguagem que fazem muito por nós desenvolvedores quando programando.

Robson, surgindo mais dúvidas, pode enviar.

Abraço.
Responder
Robson Rtp (1) (1)
23/09/2020
Valeu Thiengo, foi de grande ajuda.

Mais uma dúvida.
Em alguns tutoriais é quase costume criar as classes "model" como data classes, mas neste projeto estamos criando como classes normais.
Gostaria de saber o porquê e que diferença faz.
Responder
Vinícius Thiengo (0) (1)
27/09/2020
Robson,

Uma "data class", em resumo, explicita que a classe é uma classe que vai conter dados de domínio de problema.

Ou seja, que fará parte das classes de modelo do projeto.

Porém o que realmente ocorre na prática é que alguns métodos como equals(), hashCode() e toString() são implementados (em tempo de compilação) de maneira única para cada data class.

Algo que não ocorre quando a classe não é definida como uma data class.

Em nosso projeto, colocar as classes de domínio como data class somente porque estão no pacote /model...

... fazer isso é uma escolha imprudente se não formos utilizar em código as vantagens de ter classes definidas como data.

Isso, pois em tempo de compilação ainda mais códigos serão gerados para cada data class.

E se esses códigos extras não têm uma finalidade em lógica de negócio do projeto...

... então eles se tornam algo ruim, também conhecido como "code overload".

Ou seja, você inflou o seu projeto compilado, somente por causa de uma regra de negócio em projeto que uma parcela da comunidade Android utiliza sem realmente saber o porquê.

É isso.

Em nosso caso, somente a classe de modelo LastVideo se beneficiaria em ser uma data class.

Todas as outras: não.


Robson, surgindo mais dúvidas, pode enviar.

Abraço.
Responder