Olá! Boas-vindas a mais um curso da Alura. Meu nome é Junior Martins, e eu serei seu instrutor.
#acessibilidade: Eu sou um homem de pele clara e cabelos loiro-escuros compridos até os ombros. Estou usando um óculos de armação preta e uma camiseta também preta. Ao fundo, uma parede branca iluminada por uma luz degradê do rosa para o azul.
Durante esse curso, continuaremos trabalhando no Concord, uma aplicação que simula um chat de mensagens instantâneas.
Entenderemos alguns conceitos acerca da implementação de download de arquivos para a nossa aplicação, além do compartilhamento de mídias do armazenamento interno do app para outras pessoas via chat.
Também aprenderemos a delegar para outros aplicativos a responsabilidade da abertura de arquivos que nossa aplicação não consegue abrir, sugerindo-os outros aplicativos instalados no dispositivo da pessoa usuária que podem fazê-lo.
Para isso, estudaremos e aprenderemos a consumir o componente Share Sheet do Android, que exibe o menu de compartilhamento de arquivos com outras possíveis aplicações do dispositivo.
Por fim, entenderemos alguns conceitos que dizem respeito a como o Android lida com a escrita de arquivos dentro do sistema. Esses conceitos são um pouco mais avançados e envolvem, inclusive, a necessidade de remover nosso aplicativo da Play Store após sua publicação, caso ele não lide bem com certos tipos de permissão.
O conteúdo desse curso é importante para aprender a atualizar a sua aplicação com novas informações sem precisar atualizar a aplicação em si, realizando o download de novas mídias.
Com tudo isso, entenderemos a importância de integrar nosso app com outras aplicações, permitindo o envio de arquivos exclusivos do nosso escopo para outros, além de permitir que a pessoa usuária possa acessar um arquivo que nosso app não é capaz de reproduzir por meio de outra aplicação.
Para conseguir acompanhar esse curso, é essencial que você tenha concluído o curso anterior dessa formação, sobre armazenamento de arquivos com Jetpack Compose. Partiremos do pressuposto de que você já domina vários dos conceitos apresentados nele. Continuaremos a nossa aplicação a partir desse ponto!
Curso: Jetpack Compose: lidando com armazenamento de arquivos no Android
A qualquer momento, você pode acessar a nossa comunidade no Discord para compartilhar suas ideias e aprendizados, além de tirar dúvidas. Afinal, esse é um conteúdo um pouco mais avançado, então estaremos disponíveis para ajudar.
Esperamos que você esteja empolgado ou empolgada para começar. Vamos lá!
Nesse curso, continuaremos desenvolvendo o projeto do curso anterior, sobre armazenamento de arquivos: o aplicativo Concord, que simula um app de troca de mensagens instantâneas.
Realizamos previamente algumas modificações no projeto anterior para podermos prosseguir com esse conteúdo, que é um pouco mais avançado.
Deixamos o projeto inicial já modificado disponível para download, além de um link do GitHub evidenciando as mudanças implementadas, caso você prefira adaptar o seu próprio projeto.
Essas mudanças envolvem, principalmente, atualizações de algumas bibliotecas, como o Jetpack Compose, Hilt, etc., para trabalharmos com o que há de mais moderno no momento.
Comentaremos as mudanças mais específicas examinando o código e a aplicação. Portanto, vamos abrir o projeto atualizado no Android Studio.
Notaremos a primeira mudança no emulador rodando a nossa aplicação. Se clicarmos no chat com a Julieta ou o Romeu, além de todo o banco de dados que já tínhamos no curso anterior, teremos algumas mensagens novas enviadas por eles, contendo arquivos de mídia.
É do nosso interesse descobrir como baixar esses arquivos. Para implementar essa funcionalidade no Android Studio, vamos conferir alguns detalhes que foram adicionados.
Nossa mudança principal é que não temos mais uma única classe de mensagens que lidará com o nosso aplicativo inteiro, pois a dividimos em duas.
MessageEntity
e MessageWithFile
A primeira classe é a MessageEntity
. Ela conterá as informações básicas de cada mensagem, sendo bem próxima da classe de mensagens antiga. Ela possui todos os campos que já utilizamos e, também, o campo idDownloadableFile
, que entenderemos melhor adiante.
A MessageEntity
terá uma classe irmã chamada MessageWithFile
, ou seja, "mensagem com arquivo". Essa classe é a que mais interagirá com o nosso aplicativo inteiro, enquanto a MessageEntity
será mais direcionada às situações em que precisamos ler ou atualizar uma informação do banco.
Ambas as classes possuem um método de conversão uma para a outra: MessageEntity
possui o método toMessageWithFile
; MessageWithFile
possui o método toMessageEntity
. Os métodos já foram implementados no código.
A diferença mais importante da classe MessageWithFile
é que ela não irá interagir diretamente com o banco. Além disso, terá um campo chamado downloadableFile
: uma variável que utilizaremos em determinados momentos para poder pegar um dos arquivos que já existem no aplicativo, baixar e salvar no dispositivo.
DownloadableFile
, DownloadableFileEntity
e FileInDownload
A DownloadableFile
é uma classe que possui a mesma ideia. Temos uma classe base, chamada DownloadableFileEntity
, e a que se chama apenas DownloadableFile
. Cada uma delas será utilizada em momentos específicos.
Na classe DownloadableFile
, teremos o nome do arquivo que queremos baixar, sua URL, tamanho e status do download (sucesso, pendência ou erro).
Além disso, teremos um quarto arquivo chamado FileInDownload.kt
, também uma classe de dados. Ela será utilizada no momento em que iniciarmos o download de um arquivo, para armazenar algumas informações temporariamente, como: nome do arquivo, origem, inputStream
(que compreenderemos mais adiante) e o ID da mensagem.
FileUtils.kt
Por fim, uma última modificação relevante para o nosso projeto é a criação do arquivo FileUtils.kt
. Ainda mexeremos bastante nesse arquivo, mas, nesse momento, ele possui apenas uma função extension do tipo Long
. Essa função receberá o tamanho do arquivo em bytes e retornará esse tamanho formatado em MB, GB, KB, etc.
Inclusive, já conseguimos visualizar o resultado dessa classe no emulador do aplicativo. Quando recebemos um arquivo de mídia como mensagem, o botão de download exibe o tamanho formatado do arquivo:
Reforçamos que, caso você queira conferir essas mudanças, temos o link do GitHub para você realizar as comparações e adaptar seu projeto. Do contrário, você pode apenas baixar essa versão já pronta e seguir para o próximo passo.
Nos encontramos lá!
No curso anterior, focamos bastante em entender como funciona o armazenamento de arquivos no Android como um todo, além de como manipular todos os arquivos que possam já existir no dispositivo das nossas pessoas usuárias.
Porém, é bastante comum que aplicativos sejam capazes não apenas de manipular o que já existe, mas também obter novos arquivos externos. Portanto, dedicaremos esse primeiro momento do curso a entender como fazer isso.
Primeiramente, vamos conferir dois esquemas. O primeiro representa abstratamente o fluxo de download de um novo arquivo para dentro da nossa aplicação. Como funciona:
Normalmente, teremos um servidor de arquivos que armazena tudo o que precisamos. Também teremos o local onde ficam as informações de cada um desses arquivos, como nome, tamanho, link para download e assim por diante.
O segundo esquema é bastante similar e servirá mais especificamente para o nosso projeto. Como faremos:
Por motivos de estudo, não teremos o local de armazenamento de informações dos arquivos. O local de armazenamento externo dessas informações já está pronto no banco de dados e integrado ao nosso aplicativo.
Já o armazenamento dos arquivos está num servidor externo. Utilizaremos um repositório do GitHub para entender como realizar esses downloads. Vamos conferir tudo isso dentro do Android Studio.
Vamos abrir o nosso projeto no AS e maximizar o App Inspection. Nele, conseguimos acessar a guia que exibe o banco de dados. Já aprendemos como interagir com essa guia no curso anterior.
À esquerda, poderemos notar uma nota tabela na seção de Databases, chamada DownloadableFile
. Clicando nela, poderemos conferir cada uma das informações que, geralmente, uma API externa poderia nos fornecer, conforme comentamos.
Nesse caso, por ser um estudo e também para poupar um pouco do nosso tempo, essas informações já foram baixadas, tecnicamente. Temos o nome de cada arquivo, seu ID, o tamanho e, mais importante, a URL de download.
Esses links estão presentes em cada uma das mensagens de mídia que recebemos no emulador do aplicativo para podermos baixar esses arquivos.
Para realizar o procedimento de downloads, vamos minimizar o App Inspection e fechar todos os arquivos abertos no Android Studio.
No explorador de arquivos, vamos localizar o pacote principal do aplicativo (clicando em "java > com.alura.concord"). Dentro dele, usaremos o atalho "Alt + Insert" e pesquisaremos "package" para criar um novo pacote. Chamaremos esse pacote de "network", e ele será relacionado a todo o trabalho que realizarmos em relação à internet.
Já dentro desse pacote, criaremos um novo arquivo ("Kotlin Class/File") chamado DownloadService
, do tipo "Object". Nesse arquivo, criaremos uma função chamada makeDownloadByURL()
. Essa função receberá uma url
, que será do tipo String
. Abrimos e fechamos o corpo da nossa função com chaves.
DownloadService.kt
object DownloadService {
fun makeDownloadByURL(url: String) {
}
}
Existem algumas maneiras diferentes de realizar o download de arquivos dentro do nosso aplicativo. Optaremos pela maneira demonstrada em um evento oficial do Android (referenciado no Para Saber Mais, caso você queira conferir).
O interessante desse código é que não precisaremos adicionar nenhuma dependência externa ao projeto para realizar o download do que precisamos. Então, criaremos dentro dessa função uma variável chamada client
, que será igual a OkHttpClient()
, oriundo de "okhttp3". Resultado: val client = OkHttpClient()
.
Em seguida, criaremos uma requisição. Para isso, começaremos chamando o Request()
(oriundo do mesmo pacote "okhttp3"). Em seguida, chamaremos .Builder()
e .url()
— informação que receberemos via parâmetro, então chamamos url
dentro dos parênteses.
Por fim, chamamos .build()
, resultando em Request.Builder().url(url).build()
. Após fazer isso, adicionamos .val
e apertamos "Enter" para armazenar todo esse código em uma variável que chamaremos de request
:
fun makeDownloadByURL(url: String, context: Context) {
val client = OkHttpClient()
val request = Request.Builder().url(url).build()
}
Agora que definimos que criaremos um client
e definimos nossa variável de requisição, ainda dentro dessa função, pegaremos a variável client
e chamaremos o método .newCall()
, pois queremos realizar uma nova chamada passando a nossa request
. Com isso, daremos um .execute()
, resultando em: client.newCall(request).execute()
.
Após esse método, utilizaremos o método .use {}
, nomeando o retorno como response
:
client.newCall(request).execute().use { response -> }
Chamamos esse retorno na próxima linha, pegando o corpo (.body
) da requisição. Se o corpo da requisição não for nulo (inserimos o operador de safe call ?
), podemos pegar o fluxo de bytes, ou seja, .byteStream()
.
Com o fluxo de bytes em mãos, podemos chamar o método .use {}
mais uma vez. Resultado: response.body?.byteStream()?.use { }
.
Estamos utilizando o método use {}
em clientNewCall()
quanto no byteStream()
, porque a nossa intenção é pegar esse fluxo de dados retornado e fazer sua leitura para escrever em um arquivo. Esse método permite fazer essa leitura e, ao final do escorpo, fecha o escopo em que estamos trabalhando, diferente de um let
que poderia causar um problema nesse caso.
O retorno que teremos nesse momento será chamado de fileData ->
. Podemos pressionar "Alt + Enter" nesse nome e selecionar a opção Specify type explicitly
, resultando em fileData: InputStream? ->
. Ou seja, o retorno é do tipo InputStream
que nós conhecemos anteriormente em FileInDownload
.
O InputStream
é um tipo de dado que nos traz um fluxo de informações que podemos gravar, de alguma forma. Nesse momento, InputStream
está definido como um tipo que pode ser nulo, mas podemos tirar essa opção dele e inseri-la antes do .use
, adicionando o sinal de interrogação, garantindo uma chamada segura:
client.newCall(request).execute().use { response ->
response.body?.byteStream()?.use { fileData: InputStream -> }
}
Temos nosso fluxo de dados. Em teoria, nesse momento nós fomos até o servidor, pegamos a informação que precisamos e a temos pronta para transformar em um arquivo.
Para transformar essa informação em um arquivo, primeiro precisamos criar um arquivo vazio. Para isso, podemos chamar a classe File()
oriunda do "java.io".
Entre os parênteses, colocaremos duas informações: path
, o local onde salvaremos o arquivo, e também uma string com o nome desse arquivo, como, por exemplo, "Teste.png"
(refinaremos essa definição no decorrer das aulas). Resultado: File(path, "Teste.png")
.
A variável path
não é reconhecida por padrão. Então, podemos criá-la logo acima do File()
, com val path
. Aqui, precisamos relembrar um pouco do que aprendemos no curso anterior.
Queremos que o caminho onde salvaremos esse arquivo seja um daqueles diretórios que sabemos que pertencem ao nosso aplicativo. Portanto, poderíamos obter acesso a alguma daquelas pastas por meio de um contexto.
Então, além de a função makeDownloadByURL()
receber apenas uma url
, ela também receberá um context
que será do tipo Context
, resultando em: makeDownloadByURL(url: String, context: Context)
. Com isso, podemos voltar à declaração de path
e chamar context.getExternalFilesDir()
.
Como parâmetro, passaremos o nome "temp"
, porque queremos pegar o diretório específico do nosso app e criar uma pasta chamada "temp" dentro dele. Ao fim de File()
, podemos dar um .val()
para armazenar o resultado numa variável chamada newFile
:
client.newCall(request).execute().use { response ->
response.body?.byteStream()?.use { fileData: InputStream ->
val path = context.getExternalFilesDir("temp")
val newFile = File(path, "Teste.png")
}
}
Agora temos duas coisas: nosso fluxo de dados pronto para gravar e nosso arquivo que, no momento, foi criado e está vazio. Como juntamos essas duas informações?
O código pode ter gerado automaticamente o newFile
na linha 20, mas podemos apagá-lo. Ainda dentro dessa função, pularemos uma linha e chamaremos esse arquivo novamente, newFile
, e colocaremos um .outputStream()
, ou seja, começaremos a pegar esse arquivo para fazer algo com ele.
Se inserirmos o método .use {}
, esse arquivo estará aberto para adicionar informações dentro dele. Entre as chaves, colocaremos um file->
e, na linha seguinte, pegaremos o nosso inputStream()
, que é o fileData
, e copiar seu conteúdo para algum lugar com o método . copyTo()
, passando esse lugar como parâmetro: file
.
newFile.outputStream().use { file ->
fileData.copyTo(file)
Esse é um código um pouco extenso que pode ser difícil de compreender logo de primeira. Então, vamos fazer uma breve revisão:
client
e uma variável request
, que receberá a URL via parâmetro da função;client.NewCall(request)
;newFile
) de nome "Teste.png"
, fornecendo o caminho para salvá-lo (path
);outputStream()
.No entanto, para fazer a chamada desse método, será necessário que quem o chamar implemente alguns elementos específicos. Aprenderemos a fazer isso no próximo vídeo.
**Até lá! **
O curso Jetpack Compose: baixando e compartilhando arquivos no Android possui 131 minutos de vídeos, em um total de 50 atividades. Gostou? Conheça nossos outros cursos de Android em Mobile, ou leia nossos artigos de Mobile.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.