Este artigo descreve em detalhes os estágios de desenvolvimento do aplicativo móvel Meeting Room Helper: desde o início da ideia até o lançamento. O aplicativo é escrito em Kotlin e construído em uma arquitetura MVVM simplificada, sem o uso de ligação de dados. A parte da interface do usuário é atualizada usando objetos LiveData. As razões para recusar a ligação de dados são detalhadas e explicadas. A arquitetura utiliza várias soluções interessantes que permitem dividir logicamente o programa em arquivos pequenos, o que simplifica o suporte ao código.


Descrição do Projeto
Há 3 anos, nossa empresa teve a idéia de desenvolver um pequeno projeto para reservas instantâneas de salas de reunião. A maioria dos gerentes de RH e Arcadia prefere usar o calendário do Outlook para tais fins, mas e o resto?
Vou dar 2 exemplos da vida do desenvolvedor
- Qualquer equipe periodicamente tem um desejo espontâneo de realizar um rali rápido por 5 a 10 minutos. Esse desejo pode ultrapassar os desenvolvedores em qualquer canto do escritório e, para não distrair os colegas ao redor deles, eles (desenvolvedores e não apenas) começam a procurar uma conversa gratuita. Ao migrar de uma sala para outra (em nosso escritório, as salas de reunião são organizadas em sequência), os colegas “verificam cuidadosamente” qual das salas está atualmente livre. Como resultado, eles distraem colegas de dentro. Esses caras sempre foram e sempre serão, mesmo que o tiroteio seja encerrado no estatuto da empresa pela interrupção do rali. Quem entendeu, ele vai entender.
- E aqui está outro caso. Você acabou de sair da sala de jantar e está indo para si mesmo, mas aqui seu colega (ou gerente) de outro departamento o intercepta. Ele quer lhe dizer algo urgente e, para esses fins, você precisa de uma sala de reuniões. De acordo com os regulamentos, você deve primeiro reservar um quarto (pelo telefone ou computador) e só depois ocupá-lo. É bom se você tiver um telefone celular com o Outlook móvel. E se não? Voltar ao computador e voltar para a sala de reuniões? Para forçar cada funcionário a colocar o Outlook Express no telefone e garantir que todos os carreguem com eles? Estes não são nossos métodos.
Por isso, há 2,5 anos, cada uma das salas de reunião estava equipada com seu próprio tablet:

Para este projeto, meu colega desenvolveu a primeira versão do aplicativo: Meeting Room Little Helper (
aqui você pode ler sobre isso ). MRLH permitiu fazer uma reserva, cancelar e renovar uma reserva, mostrou o status das conversas restantes. O reconhecimento da identidade de um funcionário (usando o serviço de nuvem Microsoft Face API e nossos analisadores internos) tornou-se um "truque" inovador. O aplicativo acabou sendo sólido e serviu a empresa fielmente por 2,5 anos.
Mas o tempo passou ... Novas idéias apareceram. Eu queria algo novo e, por isso, decidimos reescrever o aplicativo.
Termos de Referência
Como costuma acontecer - mas, infelizmente, nem sempre - o desenvolvimento começou com a preparação de especificações técnicas. Primeiro, chamamos os caras que costumam usar tablets para reservas. Aconteceu que, acima de tudo, eles eram viciados em RHs e gerentes que anteriormente usavam o Outlook exclusivamente. Deles, recebemos o seguinte feedback (dos requisitos, fica imediatamente claro o que o RH solicitou e o que os gerentes pediram):
- você deve adicionar a capacidade de reservar qualquer sala de reunião a partir de qualquer tablet (anteriormente, cada tablet permitia reservar apenas sua sala);
- seria legal olhar para o cronograma de comícios para uma reunião o dia inteiro (idealmente, para qualquer dia);
- todo o ciclo de desenvolvimento deve ser realizado em um curto espaço de tempo (por 6-7 semanas).
Tudo está claro com os desejos do cliente, mas e os requisitos técnicos e o futuro? Adicione alguns requisitos para o projeto da guilda de desenvolvedores:
- O sistema deve funcionar tanto com os tablets existentes quanto com os novos;
- escalabilidade do sistema - a partir de 50 conversas ou mais (isso deve ser suficiente com uma margem para a maioria dos clientes se o sistema começar a se replicar);
- manutenção da funcionalidade anterior (a primeira versão do aplicativo usava a API Java para se comunicar com os serviços do Outlook, e planejávamos substituí-la por uma API especializada do Microsoft Graph, por isso era importante não perder a funcionalidade);
- minimização do consumo de energia (os tablets são alimentados por uma bateria externa, porque o centro de negócios não permitiu perfurar suas paredes para colocar nossos fios);
- novo design UX / UI, refletindo ergonomicamente todas as inovações.
Total de 8 pontos. Os requisitos são razoavelmente justos. Além disso, estipulamos as regras gerais de desenvolvimento:
- use apenas tecnologias avançadas (isso permitirá que a equipe se desenvolva como especialista e não fique estagnada em um só lugar, simplificando o suporte ao projeto em um futuro próximo);
- seguir as melhores práticas, mas não as tome cegamente como garantidas, pois a regra principal de qualquer profissional (e um desenvolvedor que se esforça por isso) é avaliar tudo criticamente;
- escrever código limpo e organizado (talvez seja o mais difícil quando você estiver tentando combinar inovação e tempo de desenvolvimento apertado).
Foi iniciado. Como sempre, está entusiasmado! Vamos ver o que acontece a seguir.
Desenho
Design de aplicativo desenvolvido pelo designer UX:


Esta é a tela principal. Será exibido na maioria das vezes. Todas as informações necessárias estão localizadas ergonomicamente aqui:
- o nome da sala e seu número;
- status atual;
- tempo até a próxima reunião (ou até o final);
- os status dos quartos restantes na parte inferior da tela.
Observe: o mostrador exibe apenas 12 horas, como o sistema está configurado de acordo com as necessidades da empresa (os tablets Arcadia funcionam das 8 às 20 horas, ligam e desligam automaticamente)


Para reservar um quarto, basta ligar para a janela de reservas e indicar a duração do rali. As etapas para reservar os quartos restantes são semelhantes; elas apenas começam clicando no ícone do quarto.


Se você deseja agendar uma reunião para um horário específico, vá para a próxima guia, para a lista de reuniões que ocorrerão hoje na sala de reuniões e clique em tempo livre. Além disso, tudo é como no primeiro caso.
A árvore de transição completa deve se parecer com isso:


Vamos tentar implementá-lo com competência.
Pilha de tecnologia
As técnicas de desenvolvimento estão se desenvolvendo rapidamente e mudando. Por mais 2 anos, Java foi a linguagem oficial de desenvolvimento Android. Todo mundo escreveu em Java e usou a ligação de dados. Agora, parece-me, estamos caminhando em direção à programação reativa e ao Kotlin. Java é uma ótima linguagem, mas tem algumas imperfeições em comparação com o que o Kotlin e o AndroidX têm a oferecer. Kotlin e AndroidX podem reduzir ao mínimo o uso da ligação de dados, se não a excluir completamente. Abaixo tentarei explicar meu ponto de vista.
Kotlin
Acho que muitos desenvolvedores do Android já mudaram para o Kotlin e, portanto, concordam comigo que escrever um novo projeto Android em 2019 em qualquer idioma que não seja o Kotlin é como lutar contra o mar. Claro que você pode argumentar, mas e Flutter e Dart? E quanto a C ++, C # e até Cordova? A que responderei: a escolha é sempre sua.
Em 480 aC o rei persa Xerxes ordenou que seus soldados atravessassem o mar como um castigo por destruir parte de seu exército durante uma tempestade e, cinco séculos depois, o imperador romano Calígula declarou guerra a Poseidon. Uma questão de gosto. Para 9 em 10, Kotlin é bom, mas para 10 pode ser ruim. Tudo depende de você, de seus desejos e aspirações.
Kotlin é a minha escolha. A linguagem é simples e bonita. Escrever sobre isso é fácil e agradável e, o mais importante, não é necessário escrever demais: classe de dados, objeto, setter e getter opcional, expressões lambda simples e funções de extensão. Esta é apenas uma pequena parte do que esse idioma tem a oferecer. Se você ainda não mudou para o Kotlin - fique à vontade! Na seção com prática, demonstrarei algumas das vantagens do idioma (não é uma oferta publicitária).
Model-View-ViewModel
Atualmente, o MVVM é a arquitetura de aplicativo recomendada do Google. Durante o desenvolvimento, aderiremos a esse padrão específico, no entanto, não o observaremos completamente, pois o MVVM recomenda o uso de ligação de dados, mas nós o recusamos.
Profissionais da MVVM- Diferenciação da lógica de negócios e da interface do usuário. Na implementação correta do MVVM, não deve haver um único Android de importação no ViewModel, exceto os objetos LiveData dos pacotes AndroidX ou Jetpack. O uso adequado deixa automaticamente todo o trabalho da interface do usuário dentro de fragmentos e atividades. Isso não é ótimo?
- O nível de encapsulamento é bombeado. Será mais fácil trabalhar em equipe: agora você pode trabalhar todos juntos em uma tela e não interferir entre si. Enquanto um desenvolvedor trabalha com a tela, outro pode criar um ViewModel e um terceiro pode escrever consultas no Repositório.
- O MVVM tem um efeito positivo na gravação de testes de unidade. Este item segue o anterior. Se todas as classes e métodos forem encapsulados pelo trabalho com a interface do usuário, eles poderão ser facilmente testados.
- Uma solução natural com rotação da tela. Não importa o quão estranho possa parecer, mas esse recurso é adquirido automaticamente, com a transição para o MVVM (porque os dados são armazenados no ViewModel). Se você verificar aplicativos bastante populares (VK, Telegram, Sberbank-Online e Aviasales), verifica-se que exatamente metade deles não consegue girar a tela. O que me causa alguma surpresa e mal-entendido como usuário desses aplicativos.
Por que o MVVM é perigoso?- Vazamento de memória. Este erro perigoso acontece se você infringir as leis do uso do LiveData e do observador. Examinaremos esse erro em detalhes na seção prática.
- Alastrando ViewModel. Se você tentar ajustar toda a lógica de negócios ao ViewModel, receberá um código ilegível. A saída dessa situação pode ser dividir o ViewModel em uma hierarquia ou usar Presenters. Foi exatamente o que eu fiz.
Regras para trabalhar com MVVMVamos começar com o maior número de erros e avançar para os menos erros:
- o corpo da solicitação não deve estar no ViewModel (apenas no Repositório);
- Os objetos LiveData são definidos no ViewModel, eles não se lançam dentro do Repositório, porque solicitações no Repositório são processadas usando Rx-Java (ou coroutines);
- todas as funções de processamento devem ser movidas para classes e arquivos de terceiros ("Apresentadores"), para não confundir o ViewModel e não distrair a essência.
Livedata
LiveData é uma classe de suporte de dados observável. Ao contrário de um observável regular, o LiveData reconhece o ciclo de vida, o que significa que respeita o ciclo de vida de outros componentes do aplicativo, como atividades, fragmentos ou serviços. Essa conscientização garante que o LiveData atualize apenas observadores de componentes de aplicativos que estão em um estado de ciclo de vida ativo.
Fonte: developer.android.com/topic/libraries/architecture/livedataUma conclusão simples pode ser extraída da definição: LiveData é uma ferramenta de programação reativa confiável. Vamos usá-lo para atualizar a parte da interface do usuário sem ligação de dados. Porque
A estrutura dos arquivos XML não permite uma distribuição concisa dos dados obtidos de <data> ... </data>. Se tudo estiver claro com arquivos pequenos, e os arquivos grandes? O que fazer com telas complexas, múltiplas incluem e passam vários campos? Usa modelos em qualquer lugar? Obter ligações de campo rígidas? E se o campo deve ser formatado, chame métodos de pacotes Java? Isso torna o código irremediavelmente e completamente espaguete. Não é o que a MVVM prometeu.
Rejeitar a ligação de dados tornará transparentes as alterações na parte da interface do usuário. Todas as atualizações ocorrerão diretamente dentro do observador. Porque Como o código Kolin é conciso e claro, não teremos problemas com o observador inchado. Escrever e manter o código se tornará mais fácil. Os arquivos XML serão usados apenas para design - nenhuma propriedade dentro.
A ligação de dados é uma ferramenta poderosa. É ótimo para resolver alguns problemas e se harmoniza bem com Java, mas com Kotlin ... Com Kotlin, na maioria dos casos, a ligação de dados é apenas rudimentar. A ligação de dados apenas complica o código e não oferece vantagens competitivas.
Em Java, você tinha uma escolha: use a ligação de dados ou escreva muitos códigos feios. No Kotlin, você pode acessar os elementos de exibição diretamente, ignorando findViewById (), bem como suas propriedades. Por exemplo:
Surge uma pergunta lógica: por que se preocupar com modelos de jardinagem em arquivos XML, invocando métodos Java em arquivos XML, sobrecarregando a lógica da parte XML se tudo isso pode ser evitado?
Corotinas em vez de Thread () e Rx-Java
As corotinas são incrivelmente leves e fáceis de usar. Eles são ideais para a maioria das tarefas assíncronas simples: processamento de resultados de consultas, atualização da interface do usuário etc.
As corotinas podem substituir efetivamente Thread () e Rx-Java nos casos em que não é necessário alto desempenho, porque eles pagam pela leveza com rapidez. Rx-Java, sem dúvida, é mais funcional, no entanto, para tarefas simples, todos os seus ativos não são necessários.
Microsoft e o resto
Para trabalhar com os serviços do Outlook, a API do Microsoft Graph será usada. Com as permissões apropriadas, você pode obter todas as informações necessárias sobre funcionários, salas e eventos (ahs). Para o reconhecimento de rosto, será usado o serviço de nuvem Microsoft Face API.
Olhando um pouco à frente, direi que, para resolver o problema de escalabilidade, foi usado o armazenamento em nuvem Firebase. Isso será discutido abaixo.
Arquitetura
Problemas de escalabilidade
É muito difícil tornar o sistema total ou parcialmente escalável. Isso é especialmente difícil de fazer se a primeira versão do aplicativo não for escalável e a segunda se tornar. O aplicativo v1 enviou solicitações para todos os quartos de uma vez. Cada um dos tablets enviava solicitações regularmente ao servidor para atualizar todos os dados. Ao mesmo tempo, os dispositivos não se sincronizaram porque o projeto simplesmente não possui seu próprio servidor.
Obviamente, se seguirmos o mesmo caminho e enviarmos solicitações N de cada um dos N tablets, em algum momento derrubaremos a Microsoft Graph API ou congelaremos o sistema.
Seria lógico usar uma solução cliente-servidor na qual o servidor examina o gráfico, acumula dados e, mediante solicitação, fornece informações aos tablets, mas aqui somos atingidos pela realidade. A equipe do projeto é composta por 2 pessoas (desenvolvedor e designer Android). Eles precisam cumprir o prazo de 7 semanas e o back-end não é fornecido, porque escalar é um requisito do desenvolvedor. Mas isso não significa que a ideia deva ser abandonada?
Provavelmente, a única solução certa nessa situação será o uso de armazenamento em nuvem. O Firebase substituirá o servidor e atuará como um buffer. Acontece o seguinte:
cada tablet pesquisa apenas seu endereço na API do Microsoft Graph e, se necessário, sincroniza os dados no armazenamento em nuvem, de onde podem ser lidos por outros dispositivos.A vantagem desta implementação será uma resposta rápida, porque O Firebase funciona em tempo real. Reduziremos o número de solicitações enviadas ao servidor N vezes, o que significa que o dispositivo funcionará com bateria um pouco mais. Do ponto de vista financeiro, o projeto não subiu de preço, porque Para este projeto, a versão gratuita do Firebase é suficiente com várias reservas: 1 GB de armazenamento, 10 mil autorizações por mês e 100 conexões por vez. As desvantagens podem incluir dependência de uma estrutura de terceiros, mas o Firebase inspira confiança em nós, porque É um produto estável, mantido e desenvolvido pelo Google.
A ideia geral do novo sistema era a seguinte: N tablets e uma plataforma em nuvem para sincronização de dados em tempo real. Vamos começar a projetar o próprio aplicativo.
LiveData no Repositório
Parece que estabeleci recentemente as regras de boa forma e violei imediatamente uma delas. Diferente do uso recomendado do LiveData dentro do ViewModel, neste projeto os objetos LiveData são inicializados no repositório e todos os repositórios são declarados como singleton. Porque
Uma solução semelhante está associada ao modo de aplicativo. Os comprimidos estão abertos das 8h às 20h. Todo esse tempo, apenas o Auxiliar da sala de reuniões foi lançado neles. Como resultado, muitos objetos podem e devem ter vida longa (é por isso que todos os repositórios são projetados como singleton).
No decorrer do trabalho, o conteúdo da interface do usuário é alternado regularmente, o que implica a criação e recreação dos objetos ViewModel. Acontece que, se você usar o LiveData dentro do ViewModel, para cada fragmento criado, seu próprio ViewModel será criado com um conjunto de objetos LiveData especificados. Se dois fragmentos semelhantes forem exibidos simultaneamente na tela, com ViewModel diferente e um Base-ViewModel comum, durante a inicialização haverá uma duplicação de objetos LiveData a partir do Base-ViewModel. No futuro, essas duplicatas ocuparão espaço na memória até serem destruídas pelo "coletor de lixo". Porque Se já temos um repositório na forma de um singleton e queremos minimizar o custo de recriar telas, seria sensato transferir objetos do LiveData dentro de um repositório de singleton, facilitando assim os objetos do ViewModel e acelerando o aplicativo.
Obviamente, isso não significa que você precise transferir todos os LiveData do ViewModel para o repositório, mas você deve abordar com mais cuidado esse problema e fazer sua escolha conscientemente. A desvantagem dessa abordagem é o aumento do número de objetos de vida longa, porque todos os repositórios são definidos como singleton e cada um deles armazena objetos LiveData. Mas, em um caso específico, o Meeting Room Helper não é um sinal negativo, porque o aplicativo é executado sem parar o dia todo, sem alternar o contexto para outros aplicativos.
Arquitetura resultante

- Todas as solicitações são executadas em repositórios. Todos os repositórios (no Meeting Room Helper existem 11 deles) são projetados como singleton. Eles são divididos por tipo de objetos retornados e ocultos atrás das fachadas.
- A lógica de negócios reside no ViewModel. Graças ao uso de "Apresentadores", o tamanho total de todo o ViewModel (há 6 no projeto) é inferior a 120 linhas.
- A atividade e o fragmento estão envolvidos apenas na alteração da parte da interface do usuário, usando o observador e o LiveData retornados do ViewModel.
- Funções para processar e gerar dados são armazenadas no "apresentador". Utilizou ativamente funções de permissão da Kotlin para processamento de dados.
A lógica de segundo plano foi movida para o Serviço de Intenção:
- Serviço de atualização de eventos. Serviço responsável por sincronizar os dados da sala atual nas Firebase e Graph API.
- Serviço de reconhecimento de usuário. É executado apenas no tablet principal. Responsável por adicionar novos funcionários ao sistema. Verifica uma lista de pessoas já treinadas com uma lista do Active Directory. Se novas pessoas aparecerem, o serviço as adiciona à API do Face e treina novamente a rede neural. Após a conclusão da operação, ela é desligada. Inicia quando o aplicativo é iniciado.
- O Serviço de Notificação Online notifica outros tablets de que este tablet está funcionando, ou seja, A bateria externa não está esgotada. Funciona através do Firebase.
O resultado foi uma arquitetura bastante flexível e correta do ponto de vista da distribuição de responsabilidades que atende a todos os requisitos do desenvolvimento moderno. Se, no futuro, abandonarmos a API do Microsoft Graph, o Firebase ou qualquer outro módulo, eles poderão ser facilmente substituídos por novos sem interferir no restante do aplicativo. A presença de um extenso sistema de "apresentadores" tornou possível levar todas as funções de processamento de dados além do núcleo. Como resultado, a arquitetura tornou-se clara, o que é uma grande vantagem. O problema de um ViewModel coberto de vegetação desapareceu completamente.
Abaixo, darei um exemplo do pacote comum usado em um aplicativo desenvolvido.
Prática. Assista Atualizações
Dependendo do estado da sala de reuniões, o mostrador mostra uma das seguintes condições:


Além disso, arcos temporários de comícios estão localizados ao longo do contorno do mostrador, e o centro faz uma contagem regressiva até o final da reunião ou até o início do próximo comício. Tudo isso é feito pela biblioteca de telas que desenvolvemos. Se a grade de reuniões mudou, precisamos atualizar os dados na biblioteca.
Como o LiveData é anunciado nos Repositórios, é mais lógico começar com eles.
Repositórios
FirebaseRoomRepository - uma classe responsável pelo envio e processamento de solicitações no Firebase relacionadas ao modelo da sala.
Para demonstrar, o código de inicialização do listener firebase foi ligeiramente simplificado (a função reconectar foi removida). Vamos dar uma olhada nos pontos do que está acontecendo aqui:
- o repositório foi projetado como um singleton (no Kotlin, basta substituir a palavra-chave da classe por objeto);
- inicialização de objetos LiveData;
- ValueEventListener é declarado como uma variável para evitar recriar uma classe anônima em caso de reconexão (lembre-se, simplifiquei a inicialização removendo a reconexão em caso de desconexão);
- inicialização do ValueEventListener (se os dados no Firebase mudarem, o ouvinte executará e atualizará imediatamente os dados nos objetos LiveData);
- Atualizações para objetos LiveData.
As próprias funções são movidas para um arquivo FirebaseRoomRepositoryPresenter separado e decoradas como funções de extensão.
fun MutableLiveData<List<Room>>.updateOtherRooms(rooms: MutableList<Room>) { this.postValue(rooms.filter { !it.isOwnRoom() }) }
Exemplo de função de extensão do FirebaseRoomRepositoryPresenterTambém para uma compreensão geral da imagem, darei uma lista do objeto Room.
- Classe de dados. Esse modificador gera e substitui automaticamente os métodos toString (), HashCode () e equal (). Não há mais necessidade de redefinir você mesmo.
- A lista de eventos do objeto Room. É esta lista que é necessária para atualizar os dados na biblioteca de discagem.
Todas as classes de repositórios estão ocultas atrás da classe de fachada.
object Repository {
- Acima, você pode ver uma lista de todas as classes de repositório usadas e fachadas de segundo nível. Isso simplifica o entendimento geral do código e demonstra uma lista de todas as classes de repositório conectadas.
- Uma lista de métodos que retornam referências a objetos LiveData do FirebaseRoomRepository. Os setters e getters do Kotlin são opcionais, portanto você não precisa escrevê-los desnecessariamente.
Essa organização permite que você se ajuste confortavelmente de 20 a 30 solicitações em um repositório raiz. Se o seu aplicativo tiver mais solicitações, você precisará dividir a fachada raiz em 2 ou mais.
ViewModel
BaseViewModel é o ViewModel base do qual todos os ViewModels são herdados. Ele inclui um único objeto currentRoom, usado universalmente.
- O marcador aberto significa que você pode herdar da classe. Por padrão no Kotlin, todas as classes e métodos são finais, ou seja, classes não podem ser herdadas e métodos não podem ser redefinidos. Isso é para proteger contra alterações de versão incompatíveis acidentais. Eu darei um exemplo
Você está desenvolvendo uma nova versão da biblioteca. Em algum momento, por um motivo ou outro, você decide renomear a classe ou alterar a assinatura de algum método. Ao alterá-lo, você criou acidentalmente incompatibilidade de versão. Opa ... Se você provavelmente soubesse que o método poderia ser substituído por alguém, e a classe fosse herdada, você provavelmente teria sido mais preciso e dificilmente teria atingido seu próprio pé. Para fazer isso, no Kotlin, por padrão, tudo é declarado como final e, para cancelamento, existe um modificador "aberto".
- O método getCurrentRoom () retorna um link para o objeto LiveData da sala atual do Repository, que, por sua vez, é retirado do FirebaseRoomRepository. Quando esse método é chamado, o objeto Room retornará contendo todas as informações sobre a sala, incluindo uma lista de eventos.
Para converter dados de um formato para outro, usaremos a transformação. Para fazer isso, crie um
MainFragmentViewModel e herde-o de
BaseViewModel .
MainFragmentViewModel é uma classe
derivada de BaseViewModel. Este ViewModel é usado apenas no MainFragment.
- Observe a falta do modificador aberto. Isso significa que ninguém herda da classe.
- currentRoomEvents - um objeto obtido usando a transformação. Assim que o objeto da sala atual é alterado, a transformação é executada e o objeto currentRoomEvents é atualizado.
- MediatorLiveData. O resultado é idêntico à transformação (mostrada para referência).
A primeira opção é usada para converter dados de um tipo para outro, que é o que precisávamos, e a segunda opção é necessária para executar alguma lógica de negócios. No entanto, a conversão de dados não ocorre. Lembre-se de que a importação do Android no ViewModel não é válida. Portanto, inicio solicitações adicionais a partir daqui ou reinicio os serviços conforme necessário.
Aviso importante! Para que a transformação ou o mediador funcione, alguém deve assinar um fragmento ou atividade. Caso contrário, o código não será executado, porque ninguém esperará um resultado (esses são objetos observadores).
Mainfragment
A etapa final na conversão de dados em resultado. MainFragment inclui uma biblioteca de discagem e um View-Pager na parte inferior da tela.
class MainFragment : BaseFragment() {
- Inicialização do MainFragmentViewModel. O modificador lateinit indica que prometemos inicializar esse objeto posteriormente, antes de usá-lo. O Kotlin tenta proteger o programador da escrita incorreta do código; portanto, devemos dizer imediatamente que o objeto pode ser nulo ou colocar lateinit. Nesse caso, o ViewModel deve ser inicializado pelo objeto.
- Observador-ouvinte para atualizar o dial.
- Inicializando o ViewModel. Observe que isso acontece imediatamente após o fragmento ser anexado à atividade.
- Após a criação da atividade, assinamos as alterações no objeto currentRoomEvents. Observe que eu não assino o ciclo de vida do fragmento (isto), mas o objeto viewLifecycleOwner. O fato é que na biblioteca de suporte 28.0.0 e AndroidX 1.0.0, um bug foi detectado quando o observador foi "cancelado". Para resolver esse problema, foi lançado um patch no formato viewLifecycleOwner, e o Google recomenda a assinatura. Isso corrige o problema do observador zumbi quando o fragmento morreu e o observador continua funcionando. Se você ainda estiver usando isso, substitua-o por viewLifecycleOwner.
Portanto, quero demonstrar a simplicidade e a beleza do MVVM e do LiveData sem usar a ligação de dados. Observe que neste projeto eu violei a regra geralmente aceita, colocando o LiveData no Repositório devido às especificidades do projeto. No entanto, se os movermos para o ViewModel, a imagem geral permanecerá inalterada.
Como uma cereja no bolo, preparei para você um pequeno vídeo com uma demonstração (os nomes são manchados de acordo com os requisitos de segurança, peço desculpas):

Sumário
Como resultado da aplicação no primeiro mês, alguns bugs foram revelados na exibição de comícios cruzados (o Outlook permite criar vários eventos ao mesmo tempo, enquanto o nosso sistema não). Agora, o sistema está funcionando há 3 meses. Erros ou falhas não são observados.
PS Obrigado
jericho_code pelo comentário. No Kotlin, você pode e deve inicializar a List <> no modelo usando emptyList (), para que um objeto extra não seja criado.
var events: List<Event.Short> = emptyList()