Adapter com Radio Button? O Jetpack pode te ajudar!

O objetivo desse post não é apresentar nenhuma revolução, mas apenas um jeito mais moderno de trabalhar com RecyclerView e Radio Button em seus itens. Provavelmente você já passou pela frustração de ter vários Radio Buttons selecionados, quando você queria que apenas um ficasse selecionado. Mas, com uma rápida pesquisa você chegou em algo parecido com isso:

Em resumo, o Adapter acima possui uma variável local, mCheckedPosition, que é inicializada com -1 (nada selecionado), porém, quando o usuário clica em algum Radio Button, a variável mCheckedPosition é preenchida e o método notifyDataSetChanged é chamado. A chamada desse método será responsável por executar o onBindViewHolder novamente (para cada linha) e, na primeira linha, caso a posição que ele estiver for a posição definida como selecionada (mCheckedPosition), Radio Button será marcado, caso contrário, desmarcado. É uma solução funcional, mas limitada.

Para tornar a solução mais robusta e moderna, dois componentes do Jetpack serão utilizados:
1. ViewModel
2. LiveData

Antes de mais nada, segue o visual final:

Adapter:

Adapter: recebe uma lista de pessoas e um listener por parâmetro.
selectedPerson: variável local que mantém a pessoa selecionada
updateSelectedPerson: método para atualizar a variável selectedPerson e avisar o adapter que algo mudou (notifyDataSetChanged*).
bind: atenção à 2 pontos:
1. Lógica do isChecked do RadioButton: Se a pessoa da linha atual for igual a pessoa selecionada, o Radio Button é selecionado, caso contrário, deselecionado.
2. Chamada do método do listener (onItemClicked) dentro do ClickListener do ViewGroup e do Radio Button.

* Como o objetivo do post é o uso do RadioButton sem precisar controlar a posição, foi utilizado o notifyDataSetChanged. Para manter o exemplo simples, não foi implementado o DiffUtil. Se você for utilizar a ideia em um aplicativo de verdade, é extremamente recomendado implementar o DiffUtil no seu adapter para obter uma melhor performance.

Activity:

people: lista de objetos Person.
initComponents: Inicialização do ViewModel, Adapter e ClickListener do botão.*
observeChanges: Observando o LiveData de pessoa selecionada. Caso ele mude, o método updateSelectedPerson do Adapter é chamado.
AdapterListener: implementando a interface do Listener do Adapter. Aqui, quando um item é clicado, é setado, no ViewModel, a pessoa selecionada.

* No initComponents é definido o setHasStableIds como true, para uma melhor performance do Adapter, ou seja, ao executar o notifyDataSetChanged, o ViewHolder não será recriado. Lembrando que isso só é possível porque sobrescrevemos o método getItemId no adapter.

ViewModel:

Esse ViewModel é auto-explicável, mas para deixar claro: apenas um LiveData que “segura” a pessoa selecionada, o get e o set.

Com essa solução, você terá:
1. As vantagens do ViewModel — Um local seguro para armazenar os dados da sua tela, o qual sobrevive a mudanças de configurações, tal como rotacionar a tela, ou seja, se o usuário rotacionar a tela, o Radio Button continuará selecionado.
2.
Não precisar controlar quem está selecionado com base em posição.

Segue um diagrama explicando o fluxo executado ao clicar em um item:

Críticas e sugestões são bem vindas!

Referências