Entre para a LISTA VIP da Black Friday

00

DIAS

00

HORAS

00

MIN

00

SEG

Clique para saber mais
Alura > Cursos de Mobile > Cursos de Android > Conteúdos de Android > Primeiras aulas do curso Jetpack Compose: baixando e compartilhando arquivos no Android

Jetpack Compose: baixando e compartilhando arquivos no Android

Obtendo arquivos externos - Apresentação

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.

Projeto do curso

Durante esse curso, continuaremos trabalhando no Concord, uma aplicação que simula um chat de mensagens instantâneas.

emulador do Concord. tela de chat com a personagem Julieta, que enviou várias figurinhas para a pessoa usuária. a interface é bastante similar à do WhatsApp.

O que aprenderemos?

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.

Pré-requisito

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

Compartilhe!

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á!

Obtendo arquivos externos - Review do projeto

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.

Mudanças e atualizações

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.

Classes 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.

Classes 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.

Arquivo 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:

chat com o Romeu no emulador do Concord. as mensagens exibidas são arquivos de mídia em branco, apenas exibindo o botão de download no centro. o primeiro arquivo possui 39 KB, o segundo 1 3KB e o terceiro 1 MB.

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á!

Obtendo arquivos externos - Simulando um servidor de arquivos

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.

Fluxo de download de arquivos

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:

esquema do fluxo de download. à esquerda, um ícone de tabela chamado "Arquivos". ele se liga por uma linha preta com um ícone de nuvem à sua direita, que se chama "Informações". a nuvem se liga por uma linha preta a um ícone de celular abaixo dele. entre a nuvem e o celular, estão as palavras nome, tamanho e link download.

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:

mesmo esquema de antes, mas "Arquivos" se chama GitHub e "Informações" se chama Banco de dados.

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.

Banco de dados

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.

Implementando o serviço de download

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) {
        
        }
}

Gerenciando o processo de download

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)

Recapitulando

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:

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á! **

Sobre o curso Jetpack Compose: baixando e compartilhando arquivos no Android

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:

Aprenda Android acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas