-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathMuseum.java
473 lines (361 loc) · 22.9 KB
/
Museum.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
package com.example.agendiario;
/**
*
* Creative Commons (CC) 2019 Marcos Vinícius da Silva Santos and Marcos Antonio dos Santos
*
* Licensed under the Creative Commons, Version 4.0;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://creativecommons.org/licenses/by-nc-sa/4.0/
* https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode
* Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)
*
* https://creativecommons.org/licenses/by-nc-sa/4.0/deed.pt_BR
* https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.pt
* Atribuição-NãoComercial-CompartilhaIgual 4.0 Internacional (CC BY-NC-SA 4.0)
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
*/
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
/**
Considerações Iniciais
[1] Nosso aplicativo irá fazer da biblioteca SQlite que implementa um mecanismo de
banco de dados SQL (pequeno, rápido, independente, de alta confiabilidade,
multiplataforma, compatível com versões anteriores, etc.). O SQlite é o mecanismo
de banco de dados mais usado no mundo. Esse mecanismo de banco de dados será
disponibilizado junto do aplicativo.
Saiba mais sobre o SQlite no link
https://www.sqlitetutorial.net/what-is-sqlite/
[2] Faremos uso da biblioteca de persistência de dados Room que oferece uma camada de
abstração sobre o SQlite para permitir acesso fluente e robusto ao banco de dados.
Room é uma das bibliotecas existentes dentro do conjunto “Android JetPack” apresentado
durante o Google I/O de 2018, ela auxilia os desenvolvedores criando uma abstração
das camadas de banco de dados (SQLite).
Aplicativos que processam quantidades não triviais de dados estruturados podem se
beneficiar muito da persistência desses dados localmente. O caso de uso mais comum é
armazenar as partes de dados relevantes em cache (na memória do dispositivo). Dessa
forma, quando o dispositivo não conseguir acessar a rede, o usuário ainda poderá
navegar pelo conteúdo enquanto estiver off-line. Todas as alterações de conteúdo
feitas pelo usuário serão sincronizadas com o servidor quando o dispositivo ficar
on-line novamente. Portanto, é altamente recomendável usar o Room em vez do SQLite,
porque o Room cuida desses problemas para você.
Saiba mais sobre como aplicar os recursos da Room no link
https://developer.android.com/training/data-storage/room
É necessário importar a dependência Room para o projeto do aplicativo em
Build.Gradle(app). O link apresenta como fazer isso para o seu aplicativo
https://developer.android.com/jetpack/androidx/releases/room
[3] Arquitetura do aplicativo
A arquitetura do aplicativo do Twitter é a mesma utilizada pelo Telegram em termos de
desenvolvimento, infraestrutura, segurança, atualização e recursos?
É impossível ter uma única maneira de criar aplicativos que seja a melhor (a ideal) para
todos os cenários. Dito isso, essa arquitetura recomendada é um bom ponto de partida
para a maioria das situações e dos fluxos de trabalho. Se você já tem uma boa maneira de
programar aplicativos Android que segue os princípios de arquitetura comuns, não é
necessário mudá-la.
Visão geral da arquitetura que iremos utilizar (isso não quer dizer que vamos programar
seguindo a sequencia apresentada)
Activity ou Fragment =>
ViewModel =>
LiveData 1
LiveData 2
n-LiveData
Repository => <1> ou <2>
<1> Model -> Room -> SQlite
(modelo de dados persistente)
<2> Remote Data Source -> Retrofit -> webservice
(fonte de dados de back-end remota)
Resumindo a visão geral:
Activity => ViewModel => Repository => Model -> Room -> SQlite
Fragment => ViewModel => Repository => Model -> Room -> SQlite
Activity => ViewModel => Repository => Remote Data Source -> Retrofit -> webservice
Fragment => ViewModel => Repository => Remote Data Source -> Retrofit -> webservice
Trazendo para o aplicativo 'AGENDIARIO':
MuseumActivity > MuseumViewModel > MuseumRepository > MuseumDao > DbRoomDatabase > SQlite
Essa arquitetura (ou design) cria uma experiência para o usuário consistente e
agradável. Independentemente de o usuário voltar ao aplicativo alguns minutos ou vários
dias depois de tê-lo fechado pela última vez, ele vê instantaneamente as informações do
usuário que o aplicativo mantém localmente. Se esses dados estiverem desatualizados, o
módulo de repositório do aplicativo começará a atualizá-los em segundo plano.
Para saber mais sobre 'Princípios de Arquitetura Comumns' leia em
https://developer.android.com/jetpack/docs/guide#common-principles
[4] Também utilizaremos para desenvolvimento do aplicativo um modelo didático baseado
no padrão arquitetural Model View ViewModel (MVVM). O objetivo do MVVM é prover
uma separação de responsabilidades, entre a view e sua lógica.
Separar responsabilidades? Pra quê? A criação de testes para o Android não é algo
assim tão simples. A falta de organização e a mistura entre as responsabilidades
(aplicativo e Android) pode gerar problemas de acoplamento entre o seu código
e o código do Sistema Operacional (SO), ou seja, seu teste pode ficar impossibilitado
de executar na JVM (Máquina Virtual Java - programa que carrega e executa os
aplicativos Java, convertendo os bytecodes em código executável de máquina. A JVM é
responsável pelo gerenciamento dos aplicativos, à medida que são executados), pois,
pode existir alguma dependência que necessite de classes do SO para ser executada.
O padrão (pattern) MVVM foi criado por John Gossman (em 2005). Gossman foi um
dos arquitetos do WPF (Windows Presentation Foundation) e Silverlight na Microsoft.
O MVVM assemelha-se em alguns aspectos ao padrão MVC (Model View Controller) e
ao padrão MVP (Model View Presenter). O MVVM busca estabelecer uma clara
separação de responsabilidades entre o (M)odelo de objetos (classes de negócio,
serviços externos e até mesmo de acesso a banco de dados) e a (V)iew que é a
interface, com a qual o utilizador interage.
A camada Model(Modelo) não conhece a View (Camada de apresentação) e vice-versa. Na
verdade a View conhece a camada ViewModel e se comunica com ela através de um
mecanismo conhecido por binding. A View através do data binding interage com a ViewModel
notificando a ocorrência de eventos e o disparo de comandos. A ViewModel por
sua vez, responde a esta notificação realizando alguma ação no modelo seja obtendo
algum dado, atualizando ou inserindo informações no modelo.
Model – Implementação do modelo de domínio da aplicação que inclui o modelo de
dados, regras de negócio e validações de lógica. Sendo um pouco mais detalhista,
o Model no MVVM, encapsula a lógica de negócios e os dados. O modelo nada
mais é do que o chamado modelo de domínio de uma aplicação, ou seja, as classes de
negócio que serão utilizadas em uma determinada aplicação. O modelo também contém os
papéis e também a validação dos dados de acordo com o negócio, cuja aplicação
em questão busca atender.
View – A responsabilidade da View é definir a aparência ou estrutura que o
usuário vê na tela. Deixando um pouco mais claro: ela é a entidade responsável
por definir a estrutura, layout e aparência do que será exibido na tela. Dentro
do nosso contexto, as Views são nossas Activities, Fragments e elementos visuais
criados para serem disponibilizados na tela.
ViewModel – A responsabilidade da ViewModel no contexto do MVVM, é disponibilizar
para a View uma lógica de apresentação. Detalhando: a ViewModel age como
um intermediário entre a View e o Model, é o responsável por manusear o Model
para ser utilizado pela View. Ele utiliza o databinding para notificar mudanças
aos observadores, no caso a View.
Data binding - É uma técnica que liga fontes de dados entre um provedor de
conteúdo (banco de dados ou serviço em nuvem) e seu consumidor (aplicativo)
mantendo os valores sincronizados.
[5] Práticas recomendadas (extraído e adaptado de https://bit.ly/3a7Kt9f)
"(...) Práticas recomendadas
A programação é um campo criativo, e a criação de apps Android não é uma exceção. Há
muitas maneiras de resolver um problema, seja comunicando dados entre várias atividades ou
fragmentos, recuperando dados remotos e mantendo-os localmente no modo off-line ou qualquer
outro cenário comum que os apps não triviais encontrem.
Embora as recomendações a seguir não sejam obrigatórias, a experiência mostra que
segui-las torna sua base de código mais robusta, testável e sustentável em longo prazo:
(a) Evite designar os pontos de entrada do seu app, como atividades, serviços e broadcast
receivers, como fontes de dados.
Em vez disso, eles precisam se coordenar apenas com outros componentes para recuperar
o subconjunto de dados relevante para esse ponto de entrada. Cada componente do app
tem vida curta, dependendo da interação do usuário com o dispositivo e da integridade
geral do sistema.
(b) Crie limites de responsabilidade bem definidos entre os vários módulos do seu app.
Por exemplo, não divulgue o código que carrega dados da rede em várias classes ou
pacotes na sua base de código. Da mesma forma, não defina diversas responsabilidades
não relacionadas, como armazenamento de dados em memória cache e vinculação de dados,
na mesma classe.
(c) Exponha o mínimo possível de cada módulo.
Não caia na tentação de criar "apenas aquele" atalho que expõe um detalhe de
implementação interna de um módulo. Você pode ganhar um pouco de tempo no curto
prazo, mas pagará caro por isso tecnicamente à medida que sua base de código
progredir ou aumentar.
(d) Sempre pense em como tornar cada módulo isoladamente testável.
Por exemplo, ter uma API bem definida para buscar dados da rede facilita os testes do
módulo que mantém esses dados em um banco de dados local. Se, em vez disso, você mesclar
a lógica desses dois módulos em um só lugar ou distribuir seu código de rede por toda
a base de código, será muito mais difícil, se não impossível, testá-los.
(e) Concentre-se no núcleo exclusivo do seu app para que ele se destaque de outros apps.
Não reinvente a roda escrevendo o mesmo código clichê várias vezes. Em vez disso,
concentre seu tempo e energia no que torna seu app único e deixe que os Componentes de
arquitetura do Android e outras bibliotecas recomendadas lidem com o clichê repetitivo.
O 'clichê', no sentido figurado, é uma ideia já muito batida, uma fórmula muito repetida
de falar ou escrever e até mesmo programar. Quando pensamos em um app devemos fazê-lo de
modo a criar uma nova experiência para o usuário ou ajudar a solucionar um problema
que impacta a vida do nosso usuário. Dito de outro modo : alguém aqui, em sã consciência,
vai reeinventar 'a roda' ou o aplicativo TikTok?
Procure criar um app pequeno e passe a dominar com calma a sua base de código. Entender
e compreender cada parte do código é fundamental para a sua evolução como programador
de aplicativos.
(f) Aplique o máximo de persistência possível em dados relevantes e atualizados.
Dessa forma, os usuários podem aproveitar a funcionalidade do seu app mesmo quando o
dispositivo estiver no modo off-line. Lembre-se de que nem todos os seus usuários têm
conectividade constante e de alta velocidade.
No Brasil, a velocidade média da internet no celular é de 13 Mb/s (Maio/2019) de acordo
com pesquisa divulgada pela empresa Opensignal (https://www.opensignal.com/). Veja em
alguns outros países: Coréia do Sul (52,4 Mb/s), Noruega (48,2 Mb/s), Canadá (42,5 Mb/s),
Itália (19,9 Mb/s), Israel (13,6 Mb/s) Argentina (12,8 Mb/s), Bolívia (12,5 Mb/s),
Nepal (4,4 Mb/s) e Iraque (1,6 Mb/s). Há uma previsão feita pela empresa Cisco
(https://bit.ly/3egFFll) que no ano de 2023 a velocidade média chegará a 43,9 Mb/s e
a 575,0 Mb/s no padrão 5G.
A persistência é ideal pelos seguintes motivos:
(i) Seus usuários não perderão dados se o SO Android destruir seu app para liberar
recursos.
(ii) Seu app continuará funcionando se uma conexão de rede estiver lenta ou
indisponível. Baseando seu app em classes de modelo com responsabilidade bem
definida de gerenciamento dos dados, ele se torna mais testável e consistente.
(g) Designe uma fonte de dados como a única fonte da verdade.
Sempre que seu app tiver que acessar esse dado, ele precisará vir dessa única fonte da
verdade.
Trocando em miúdos, nada de possuir o mesmo dado espalhado em vários locais diferentes.
[6] Sugestões de consultas:
- Guia do Desenvolvedor em https://developer.android.com/guide
- Guia Prático em https://guides.codepath.com/android
*/
/**
Anotação '@Entity'
Por fazermos uso da Room teremos que possuir um conjunto de dados relacionados
que são definidos como entidades, ou seja, para cada entidade, uma tabela é criada
no banco de dados (objeto Database) asssociado para armazenar os itens.
O nome da tabela 'museums' deverá ser informada no objeto Database. Se após a primeira
execução com sucesso do aplicativo haver a necessidade de alguma mudança na estrutura
de dados que foi definida AQUI nesta classe 'Museum' uma nova versão do banco de dados
deverá ser informada na classe 'DbRoomDatabase', afinal de contas é necessário avisar
na classe 'DbRoomDatabase' que uma mudança ocorreu aqui. A classe 'DbRoomDatabase' não
foi codificada até este momento.
Detalhe importante [1] - os dados contidos na tabela 'museums' e em outras tabelas
do banco de dados serão perdidos a não ser que você faça a migração de dados. Para
saber mais e aprender como fazer a sua migração de dados leia
https://developer.android.com/training/data-storage/room/migrating-db-versions
https://proandroiddev.com/migrating-to-room-in-the-real-world-part-1-a16358e9027
https://bit.ly/3aZDnVF
https://www.zoftino.com/database-migration-with-room
Detalhe importante [2] - se não indicar um novo número de versão o aplicativo irá
falhar (crash), ou seja, parar de funcionar. Leia mais sobre crash em
https://developer.android.com/topic/performance/vitals/crash
Detalhe importante [3] - tenha cuidado ao nomear suas tabelas, pois, os nomes das
tabelas no SQLite são indiferentes a maiúsculas.
A estrutura de dados definida AQUI para esta classe/entidade faz parte do seu estudo
sobre modelagem de dados. Essa modelagem é originária de muitos estudos e análises
realizada junto ao seu seu cliente com o levantamento de todas as necessidades, dados e
requisitos necessários, inclusive com pesquisas externas, para o desenvolvimento do
aplicativo buscando atender a máxima defendida pelo seu professor que é
"Nós temos a solução para o seu problema".
O cliente deste aplicativo é o Felipe que tem uma agenda em papel com vários espaços
para anotações onde ele registra os livros, museus, filmes e etc. Felipe faz avalições
destes e outros temas. Há um espaço para avaliação diária de questões ligadas ao seu
dia-a-dia. Relatórios com estatísticas e alertas serão disponibilizados para o
aplicativo. Desta breve descrição veio o nome do aplicativo - 'AGENDIARIO' - ou seria
melhor, 'AGENDARIO' (agenda+diário).
https://developer.android.com/training/data-storage/room/defining-data
*/
@Entity(tableName = "museums")
/**
classe 'Museum'
Representa a estrutura de dados da entidade/tabela MUSEU. As colunas (campos da tabela)
ID, NAME, STYLE, SCORE e CREATIONDATE serão criadas como membros privados da classe. As
restricões de cada coluna serão informadas com o uso da anotação '@' de acordo com o
tipo correspondente.
*/
public class Museum {
/**
Colunas, restrições e construtor da classe
Para manter um campo, o Room precisa ter acesso a ele. Você fará isso criando
membros privativos da classe e em seguida criará os "getter" e "setter" públicos
para esse campo.
Destaco que o Room utiliza as convenções (padrões) do JavaBeans, ou seja,
um 'acordo' para especificar estruturas de dados disfarçadas de classe com um
construtor vazio para a classe. Porém para nossos estudos nosso construtor não será
vazio.
Para construir os membros da classe:
[1] utilizar o menu FILE > SETTINGS
[2] expandir o item EDITOR > CODE STYLE > JAVA
[3] clique na guia CODE GENERATION
[4] na caixa FIELD digite m
[5] na caixa STATIC FIELD digite s
Com isso indicamos ao Android Studio como proceder para criar os gets e sets a
partir dos (m)embros da classe.
Escreva a lista de membros utilizando o modificador de acesso privado:
private int mId;
private String mName;
private String mStyle;
private int mScore;
private String mCreationDate;
Ao definir os membros como privativos da classe estamos definindo a capacidade
da classe em ocultar os detalhes de sua implementação (construção do código)
para outras classes e programas externos. Isso é importante, pois, a comunicação
entre a classe e outros programas somente se dará através do uso de uma API.
API (Application Programming Interface) é uma forma de integrar sistemas,
possibilitando benefícios como a segurança dos dados, facilidade no intercâmbio
entre informações com diferentes linguagens de programação e a cobrança por
acessos (monetização). Já que falei de monetização, pense no conjunto de dados
que o aplicativo 'AGENDARIO' poderá coletar e armazenar em nuvem.
Clique com o botão direito do mouse sobre qualquer um dos membros e escolha
a opção GENERATE... e depois GETTER AND SETTER
Na janela que será apresentada certifique-se que os membros tem seu nome
iniciados por m . Selecione todos os membros com o uso de CTRL+clique
e depois confirme no botão OK a criação dos métodos acessores get e set. Os métodos
acessores servem para pegarmos (get) ou definirmos (set) informações dos
membros (variáveis) da classe que são definidas como 'private'
Encontre os membros e faça as anotações '@' das restrições necessárias. São elas:
@PrimaryKey - marca um campo como campo de chave primária.
@ForeignKey - define a restrição de chave estrangeira.
@Ignore - ignora o campo marcado, não fará parte da tabela.
@ColumnInfo - especifica o nome da coluna no banco de dados, em vez de usar o
nome do campo como nome da coluna.
@Index - cria índice para acelerar as consultas.
@Embedded - marca um campo como incorporado que transforma todos os subcampos
da classe incorporada como colunas da entidade.
@Relation - especifica relações, úteis para buscar entidades relacionadas
Caso seja necessário, por exemplo, converter o conteúdo textual em maiúsculo e
remover espaços em branco do membro você pode fazer isso no 'set' do membro. Acredite,
fazer isso aqui nesta classe vai te poupar alguns detalhes de programção e
manutenção do código mais a frente em função destas regras estarem definidas aqui.
Veja o exemplo:
public void setName(String name) {
mName = name.toUpperCase().trim();
}
Clique com o botão direito do mouse sobre o nome da classe 'Museum' e escolha
a opção GENERATE... e depois CONSTRUCTOR
Na janela que será apresentada selecione os membros que farão parte do construtor
da classe com o uso de CTRL+clique e depois confirme no botão OK. O construtor é
definido como sendo um método cujo nome deve ser o mesmo nome da classe e sem
indicação do tipo de retorno (nem mesmo void). O construtor é unicamente
chamado (invocado) no momento da criação do objeto que representa a classe através
do operador new (instância da classe com = new). O retorno do operador new é
uma referência para o objeto recém-criado.
*/
@PrimaryKey(autoGenerate = true)
@NonNull
@ColumnInfo(name = "museumId")
private int mId;
@Nullable
@ColumnInfo(name = "museumName")
private String mName;
private String mStyle;
public Museum(@Nullable String name, String style, int score, String creationDate) {
mName = name;
mStyle = style;
mScore = score;
mCreationDate = creationDate;
}
public int getId() {
return mId;
}
public void setId(int id) {
mId = id;
}
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
}
public String getStyle() {
return mStyle;
}
public void setStyle(String style) {
mStyle = style;
}
public int getScore() {
return mScore;
}
public void setScore(int score) {
mScore = score;
}
public String getCreationDate() {
return mCreationDate;
}
public void setCreationDate(String creationDate) {
mCreationDate = creationDate;
}
private int mScore;
private String mCreationDate;
}