Conectar à rede

Para realizar operações de rede no seu aplicativo, o manifesto precisa incluir as seguintes permissões:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Práticas recomendadas para comunicação de rede segura

Antes de adicionar a funcionalidade de rede ao seu app, você precisa garantir que os dados e as informações dele permaneçam seguros quando forem transmitidos pela rede. Para fazer isso, siga estas práticas recomendadas de segurança de rede:

Para mais informações sobre como aplicar princípios de rede seguros, consulte dicas de segurança na rede.

Escolher um cliente HTTP

A maioria dos apps Android conectados à rede usa HTTP para enviar e receber dados. A plataforma Android inclui o cliente HttpsURLConnection, que oferece suporte à TLS, uploads e downloads em streaming, tempos limite configuráveis, IPv6 e pooling de conexão.

Também estão disponíveis bibliotecas de terceiros que oferecem APIs de nível superior para operações de rede. Elas oferecem suporte a vários recursos de conveniência, como a serialização de corpos de solicitação e a desserialização de corpos de resposta.

  • Retrofit (link em inglês): um cliente HTTP de tipo seguro para a JVM do Square, criado com base no OkHttp. A Retrofit permite criar uma interface do cliente de maneira declarativa e oferece suporte a várias bibliotecas de serialização.
  • Ktor (link em inglês): um cliente HTTP da JetBrains, criado totalmente para Kotlin e com corrotinas. O Ktor oferece suporte a vários mecanismos, serializadores e plataformas.

Resolver consultas DNS

Os dispositivos com o Android 10 (nível 29 da API) e versões mais recentes têm suporte integrado para buscas DNS especializadas com pesquisas de texto não criptografado e com o modo DNS via TLS. A API DnsResolver fornece resolução assíncrona genérica, que permite que você pesquise por SRV, NAPTR e outros tipos de registro. A análise da resposta é deixada para execução pelo app.

Em dispositivos com o Android 9 (nível 28 da API) e versões anteriores, o resolvedor de DNS da plataforma é compatível apenas com registros A e AAAA. Isso permite pesquisar os endereços IP associados a um nome, mas não é compatível com nenhum outro tipo de registro.

Para apps baseados em NDK, consulte android_res_nsend.

Encapsular operações de rede com um repositório.

Para simplificar o processo de execução de operações de rede e reduzir a duplicação de código em várias partes do app, você pode usar o padrão de design do repositório. Um repositório é uma classe que processa operações de dados e fornece uma abstração limpa da API sobre alguns dados ou recursos específicos.

Você pode usar a Retrofit para declarar uma interface que especifica o método HTTP, o URL, os argumentos e o tipo de resposta para operações de rede, como no exemplo a seguir:

Kotlin

interface UserService {
    @GET("/users/{id}")
    suspend fun getUser(@Path("id") id: String): User
}

Java

public interface UserService {
    @GET("/user/{id}")
    Call<User> getUserById(@Path("id") String id);
}

Em uma classe de repositório, as funções podem encapsular operações de rede e expor os resultados. Esse encapsulamento garante que os componentes que chamam o repositório não precisem saber como os dados são armazenados. As mudanças futuras na forma como os dados são armazenados também são isoladas na classe do repositório. Por exemplo, você pode ter uma mudança remota, como uma atualização de endpoints de API, ou pode implementar um armazenamento em cache local.

Kotlin

class UserRepository constructor(
    private val userService: UserService
) {
    suspend fun getUserById(id: String): User {
        return userService.getUser(id)
    }
}

Java

class UserRepository {
    private UserService userService;

    public UserRepository(
            UserService userService
    ) {
        this.userService = userService;
    }

    public Call<User> getUserById(String id) {
        return userService.getUser(id);
    }
}

Para evitar a criação de uma interface não responsiva, não execute operações de rede na linha de execução principal. Por padrão, o Android exige que você execute operações de rede em uma linha de execução diferente da linha de execução de interface principal. Se você tentar executar operações de rede na linha de execução principal, uma NetworkOnMainThreadException será gerada.

No exemplo de código anterior, a operação de rede não é realmente acionada. O autor da chamada do UserRepository precisa implementar as linhas de execução usando corrotinas ou a função enqueue(). Para mais informações, consulte o codelab Receber dados da Internet, que demonstra como implementar linhas de execução usando corrotinas do Kotlin.

Sobreviver às alterações de configuração

Quando ocorre uma mudança de configuração, como uma rotação de tela, o fragmento, ou a atividade, é destruído e recriado. Todos os dados que não estiverem salvos no estado da instância para a atividade do fragmento, que precisam conter apenas pequenas quantidades de dados, são perdidos. Se isso acontecer, talvez será necessário fazer as solicitações de rede novamente.

Você pode usar um ViewModel para permitir que seus dados sobrevivam a mudanças na configuração. O componente ViewModel foi projetado para armazenar e gerenciar dados relacionados à interface considerando o ciclo de vida. Com o UserRepository acima, o ViewModel pode fazer as solicitações de rede necessárias e fornecer o resultado para seu fragmento ou atividade usando LiveData:

Kotlin

class MainViewModel constructor(
    savedStateHandle: SavedStateHandle,
    userRepository: UserRepository
) : ViewModel() {
    private val userId: String = savedStateHandle["uid"] ?:
        throw IllegalArgumentException("Missing user ID")

    private val _user = MutableLiveData<User>()
    val user = _user as LiveData<User>

    init {
        viewModelScope.launch {
            try {
                // Calling the repository is safe as it moves execution off
                // the main thread
                val user = userRepository.getUserById(userId)
                _user.value = user
            } catch (error: Exception) {
                // Show error message to user
            }

        }
    }
}

Java

class MainViewModel extends ViewModel {

    private final MutableLiveData<User> _user = new MutableLiveData<>();
    LiveData<User> user = (LiveData<User>) _user;

    public MainViewModel(
            SavedStateHandle savedStateHandle,
            UserRepository userRepository
    ) {
        String userId = savedStateHandle.get("uid");
        Call<User> userCall = userRepository.getUserById(userId);
        userCall.enqueue(new Callback<User>() {
            @Override
            public void onResponse(Call<User> call, Response<User> response) {
                if (response.isSuccessful()) {
                    _user.setValue(response.body());
                }
            }

            @Override
            public void onFailure(Call<User> call, Throwable t) {
                // Show error message to user
            }
        });
    }
}

Para saber mais sobre este tópico, consulte estes guias relacionados: