Solucionado (ver solução)
Solucionado
(ver solução)
19
respostas

Meu app não funciona em api abaixo de 29

Olá, professor Alex,

Eu fiz os cursos que você promoveu de android, navigation, fragment, Kotlin e Firebase Firestore e desenvolvi meu primeiro app, contudo o layout fica totalmente desconfigurado em api abaixo de 29 e quando o usuário muda de tela o layout fica sobreposto, o minSdkVersion é 24. Tentei uma solução buscando informações no Google, mas não consegui encontrar.

Abaixo o build.gradle(app):

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
    id 'kotlin-android-extensions'
    id 'androidx.navigation.safeargs.kotlin'
    id 'com.google.gms.google-services'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.1"

    defaultConfig {
        applicationId "br.com.antonio.distribuidoradoguila"
        minSdkVersion 24
        targetSdkVersion 30
        versionCode 3
        versionName "1.2"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {

    //  Serialization
    implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1"

    //  Kotlin
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.6.0'

    //  Glide https://github.com/bumptech/glide
    implementation 'com.github.bumptech.glide:glide:4.11.0'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
    implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
    kapt 'com.github.bumptech.glide:compiler:4.11.0'

    //  Room
    def room_version = '2.3.0'

    implementation "androidx.room:room-runtime:$room_version"
    kapt "androidx.room:room-compiler:$room_version"

    //  Coroutines
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1"

    // Koin - Scope, View Model e Fragment
    implementation "org.koin:koin-androidx-scope:2.2.0-alpha-1"
    implementation "org.koin:koin-androidx-viewmodel:2.2.0-alpha-1"
    implementation "org.koin:koin-androidx-fragment:2.2.0-alpha-1"

    // ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
    // LiveData
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"

    // Navigation
    implementation "androidx.navigation:navigation-fragment-ktx:2.3.5"
    implementation "androidx.navigation:navigation-ui-ktx:2.3.5"

    // Preference
    implementation "androidx.preference:preference-ktx:1.1.1"

    // Firebase Authentication
    implementation platform('com.google.firebase:firebase-bom:26.3.0')
    implementation "com.google.firebase:firebase-analytics:19.0.1"
    implementation "com.google.firebase:firebase-auth-ktx:21.0.1"

    // Firebase Firestore
    implementation 'com.google.firebase:firebase-firestore-ktx:23.0.3'

    // Validador Caelum
    implementation 'br.com.caelum.stella:caelum-stella-core:2.1.2'

    // Firebase Cloud Messaging
    implementation 'com.google.firebase:firebase-analytics:19.0.1'
    implementation 'com.google.firebase:firebase-messaging:22.0.0'

    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.4.1'
    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'com.google.android.material:material:1.4.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    implementation 'org.jetbrains.kotlin:kotlin-reflect:1.4.32'



}
19 respostas

Ola Antonio, tudo bem ?

Cara, é um pouco complicado falar assim só vendo as dependências, que ao meu ver estão corretas.

Talvez tu pode ter feito alguma configuração no layout que não foi muito legal e por isso está com esse problema.

O problema aqui é que ao mudar os fragments eles não "desaparecem", ficam sobrepostos como na imagem (são 4 telas no exemplo). E isso só acontece em API de 28 abaixo. E não consigo encontrar a solução.

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Como você está fazendo a troca deles ?

Estou usando o navigation

Este é o fragment da página de login que vai para a lista de produtos, utilizando o FragmentDirections. A função é vaiParaListaProdutos()

class LoginFragment : Fragment() {

    private val controlador by lazy { findNavController() }
    private val viewModel: UsuarioViewModel by inject()
    private val estadoAppViewModel: EstadoAppViewModel by sharedViewModel()

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View? {
        return inflater.inflate(R.layout.login, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        estadoAppViewModel.temComponentes = ComponentesVisuais()

        if (viewModel.estaLogado()) {
            vaiParaListaProdutos()
        }

        configuraBotaoLogin()
        configuraBotaoCadastro()
        configuraRedefinicaoDeSenha()
    }

    private fun configuraRedefinicaoDeSenha() {
        login_redefine_senha.setOnClickListener {
            it?.let {
                LoginFragmentDirections.acaoLoginParaRedefineSenha()
                    .let(controlador::navigate)
            }
        }
    }

    private fun configuraBotaoCadastro() {
        login_botao_cadastrar_usuario.setOnClickListener {
            val direcao = LoginFragmentDirections.acaoLoginParaCadastroLogin()
            controlador.navigate(direcao)
        }
    }

    private fun configuraBotaoLogin() {
        login_botao_logar.setOnClickListener {

            limpaCampos()

            val email = login_email.editText?.text.toString()
            val senha = login_senha.editText?.text.toString()

            if (validaCampos(email, senha)) {
                autentica(Login(email, senha))
            }

            login_botao_logar.text = ACESSANDO

            it?.escondeTeclado()
        }
    }

    private fun autentica(login: Login) {
        if (temConexao()) {
            viewModel.autentica(login)
                .observe(viewLifecycleOwner, {
                    it?.let { recurso ->
                        if (recurso.dado) {
                            viewModel.geraToken()
                            vaiParaListaProdutos()
                        } else {
                            val mensagemDeErro = recurso.erro
                            if (mensagemDeErro != null) {
                                login_botao_logar.text = ENTRAR
                                view?.snackBar(mensagemDeErro)
                            }
                        }
                    }
                })
        }else {
            view?.snackBar(SEM_CONEXAO)
        }

    }

    private fun validaCampos(email: String, senha: String): Boolean {
        var valido = true

        if (email.isBlank()) {
            login_email.error = EMAIL_OBRIGATORIO
            valido = false
        }
        if (senha.isBlank()) {
            login_senha.error = SENHA_OBRIGATORIA
            valido = false
        }
        return valido
    }

    private fun limpaCampos() {
        login_email.error = null
        login_senha.error = null
    }

    private fun vaiParaListaProdutos() {
        val direcao = LoginFragmentDirections.acaoLoginParaListaProdutos()
        controlador.navigate(direcao)
    }

    fun temConexao(): Boolean {
        val conexao: ConnectivityManager =
            activity?.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        val capacidadeDeConexao = conexao.getNetworkCapabilities(conexao.activeNetwork)
        val conexaoAtiva =
            capacidadeDeConexao?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) ?: false

        return conexaoAtiva
    }


}

Oi Antonio, blz? Cara eu ainda não vi esse problema acontecendo no Navigation, pois parece um comportamento quando adicionamos um Fragment ao invés de fazer a substituição com o replace... Sendo assim, você pode compartilhar o projeto com a gente via GitHub? Dessa forma, fica mais fácil de testar, simular e investigar com mais precisão o motivo do problema. Também, se possível, tente descrever com mais detalhes quais são as interações realizadas para apresentar o problema, pois agiliza mais a nossa avaliação no projeto ;)

[]s

Olá, este é o link do projeto: https://github.com/TonyAllenJr/guila_delivery

Ele já está na Play Store com o nome Guila Delivery.

Bem, na API 29 em diante ele está funcionando normalmente. O problema ocorre da API 28 e anteriores em que ao fazer a mudança de fragment ele não "desaparece" para o usuário, gerando várias telas sobrepostas. Além disso o fundo, por padrão é branco, mas nessas APIs fica preto.

Opa Antonio, eu tentei acessar o projeto e está apresentando a seguinte exception

java.lang.AssertionError: Method getAlpnSelectedProtocol not supported for object SSL socket over Socket

Então indica o endereço do firestore... Consegue verificar/corrigir para que eu consiga testar?

[]s

Oi, Alex. Eu pesquisei este erro no Google e achei estes dois links: https://github.com/firebase/firebase-android-sdk/issues/2642 e https://stackoverflow.com/questions/64668851/why-the-firestore-isnt-working-on-android-studio

Tem alguma relação?

Baixei o código do github e para mim não deu este erro.

Não faço ideia do que se refere este erro.

Como eu poderia resolver este problema?

Em tempo: recebi este email da Google:

*Dear Customer,

We have detected a publicly accessible Google API key associated with the following Google Cloud Platform project:

Project Distribuidora do Guila (id: distribuidora-do-guila) with API key

The key was found at the following URL: https://github.com/TonyAllenJr/guiladelivery/blob/b763043259089f41d7b2dd947eb82e0d6714a650/app/google-services.json

We believe that you or your organization may have inadvertently published the affected API key in public sources or on public websites (for example, credentials mistakenly uploaded to a service such as GitHub.)

Please note that as the project/account owner, you are responsible for securing your keys. Therefore, we recommend that you take the following steps to remedy this situation:

If this key is intended to be public (or if a publicly accessible key isn’t preventable): Log in to the Google Cloud Console and review the API and billing activity on your account, ensuring the usage is in line with what you expected. Add API key restrictions to your API key, if applicable. If this key was NOT meant to be public: Regenerate the compromised API key: Search for Credentials in the cloud console platform, Edit the leaked key, and use the Regenerate Key button to rotate the key. For more details, review the instructions on handling compromised GCP credentials. Take immediate steps to ensure that your API key(s) are not embedded in public source code systems, stored in download directories, or unintentionally shared in other ways. Add API key restrictions to your API key, if applicable. The security of your Google Cloud Platform account(s) is important to us.*

Isso significa que este arquivo json não deveria ser compartilhado? Eu o exclui do github. Desculpe-me por ter tantas dúvidas, mas ainda estou iniciando.

Obrigado pela atenção.

O problema que está indicando é sobre o arquivo de configuração do Firebase mesmo. Esse arquivo não deve ser compartilhado ou público, apenas quem desenvolve o projeto deve ter acesso a ele. Dado que eu tive acesso quando me mandou o link, eu baixei novamente o projeto e coloquei o arquivo. Ele não está mais quebrando, mas, não sai da tela de apresentação e fica em looping com essa mensagem:

[Firestore]: Listen for Query(target=Query(usuarios order by __name__);limitType=LIMIT_TO_FIRST) failed: Status{code=PERMISSION_DENIED, description=Missing or insufficient permissions., cause=null}

Não sei o motivo do problema e porque essa mensagem está aparecendo.

Acho que tem a ver com as regras de segurança do firebase, talvez alguma configuração que eu coloquei não dá permissão. Não consegui achar o erro. Tem algum contato seu que posso enviar a tela das regras que eu implementei ou posso compartilhar por aqui?

Entretanto, a imagem inicial é esta abaixo? Se for, basta fazer um cadastro para poder acessar. Quando o usuário não está cadastrado aparece esta mensagem mesmo no logcat.

Insira aqui a descrição dessa imagem para ajudar na acessibilidade

Olá, Alex. Conseguiu fazer o cadastro e testar?

Oi Antonio, tudo bem? Cara, desculpe a demora, está bem corrido esses dias pra mim por conta das demandas, criação de conteúdo etc... Eu rodei novamente e ainda apresenta o mesmo problema, tá com um cara que precisa configurar as regras de acesso do Firestore, pois tudo indica que é uma configuração que está bloqueando o acesso e não está deixando abrir o App... Lembrando que estou com o arquivo google-services.json.

Olá, Alex, beleza? Tranquilo, meu amigo, sem problema, eu sou muito grato a você por estar me ajudando no meu app e no meu desenvolvimento profissional e o fato de você poder reservar um tempo para me auxiliar mostra o quão sério você. Obrigado de coração.

Bem, o motivo inicial do meu pedido de ajuda é que no emulador os fragments estavam sendo sobrepostos, certo? Mas testei aqui em um celular real com android 7.1.1 api 25 e ao passar de uma tela para outra está normal, talvez seja alguma coisa no meu emulador, o problema é que (e eu havia me esquecido de relatar) o background está sempre na cor preta.

Eis o meu style.xml:

<resources>
    <style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="android:windowBackground">#ffffff</item>
        <item name="android:textColor">#808080</item>
    </style>

    <style name="SplashScreenTheme" parent="Theme.AppCompat.DayNight.DarkActionBar">
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="android:textColor">#808080</item>
        <item name="android:windowBackground">@drawable/splash_bg</item>
    </style>
</resources>

Fiz uma simulação de pedido em um dispositivo com api 30:

Primeira tela api 30 https://firebasestorage.googleapis.com/v0/b/distribuidora-do-guila.appspot.com/o/tela-app%2Fapi-30-1.png?alt=media&token=095ad74d-b0f5-4f6f-82f7-50df35019e82

Segunda tela api 30 https://firebasestorage.googleapis.com/v0/b/distribuidora-do-guila.appspot.com/o/tela-app%2Fapi-30-2.png?alt=media&token=7ffe6333-4697-4965-9fbf-15367eeb1fbd

Terceira tela api 30 https://firebasestorage.googleapis.com/v0/b/distribuidora-do-guila.appspot.com/o/tela-app%2Fapi-30-3.png?alt=media&token=e8544f7f-b616-4844-929a-10766a7fcbb7

Quarta tela api 30 https://firebasestorage.googleapis.com/v0/b/distribuidora-do-guila.appspot.com/o/tela-app%2Fapi-30-4.png?alt=media&token=a0ad7535-3b1f-400f-8d51-046e09eee692

Perceba que o fundo é branco conforme descrito no styles.xml, este é o comportamento desejável para o app.

Agora veja o resultado utilizando um dispositivo android 7.1.1 api 25:

Primeira tela api 25 https://firebasestorage.googleapis.com/v0/b/distribuidora-do-guila.appspot.com/o/tela-app%2Fapi-25-1.jpg?alt=media&token=863e416f-1f71-47e5-992b-9c64ba79a7e1

Segunda tela api 25 https://firebasestorage.googleapis.com/v0/b/distribuidora-do-guila.appspot.com/o/tela-app%2Fapi-25-2.jpg?alt=media&token=2840a490-0791-4b5d-afe1-64924c3f184e

Terceira tela api 25 https://firebasestorage.googleapis.com/v0/b/distribuidora-do-guila.appspot.com/o/tela-app%2Fapi-25-3.jpg?alt=media&token=75ea6b9b-249d-4b61-8452-46362ddd458b

Quarta tela api 25 https://firebasestorage.googleapis.com/v0/b/distribuidora-do-guila.appspot.com/o/tela-app%2Fapi-25-4.jpg?alt=media&token=7a81a0c4-4f59-4e4c-ab6b-c60329afc938

Veja que o fundo fica preto fazendo com que alguns caracteres desapareçam. Fiz o teste alterando no styles para uma cor vermelha:

Fundo vermelho api 30: https://firebasestorage.googleapis.com/v0/b/distribuidora-do-guila.appspot.com/o/tela-app%2Fapi-30-fundo-vermelho.png?alt=media&token=2d212a0d-4c03-44d0-b263-d8ce24255766

Fundo vermelho api 25: https://firebasestorage.googleapis.com/v0/b/distribuidora-do-guila.appspot.com/o/tela-app%2Fapi-25-fundo-vermelho.png?alt=media&token=09aa98ed-a585-4649-815c-11dcd9b5b777

Na api 25, só altera a cor do bottom navigation. Então, da api 29 o fundo é branco, abaixo disso o fundo fica preto.

Eis o main.activity:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_lista_produtos_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/main_activity_nav_host"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@id/main_activity_bottom_navigation"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/nav_graph">

    </fragment>

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/main_activity_bottom_navigation"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:menu="@menu/menu_principal_bottom_navigation"
        android:background="?android:attr/windowBackground" />

</androidx.constraintlayout.widget.ConstraintLayout>

Coloquei a linha android:background="?android:attr/windowBackground" no mas continua do mesmo jeito. O android studio ao passar o mouse no diz "Replace the tag with FragmentContainerView", quando faço a substituição o app quebra.

Talvez seja algo até simples de resolver, mas não estou conseguindo encontrar uma solução.

Reitero meu respeito e agradecimento. Abraços!

Na implementação do FragmentContainerView você pode fazer da seguinte maneira:

A parte do fundo realmente precisa fazer alguns testes. Em alguns projetos eu também tive problemas similares e fazia uma busca em fóruns gerais pra ver se alguém tinha uma solução alternativa, como por exemplo, neste tópico do stackoverflow

Note que ele indica a mesma config que vc fez, mas também tem uma sugestão a mais para adicionar o <item name ="android:colorBackground"></item>. Então de fato é algo que precisa testar mesmo...

E eu que agradeço a sua paciência, realmente tá bem complicado pra mim, reunião atrás de reunião fora a produção de conteúdo, mas vou tentar te auxiliar o máximo que eu puder até resolver o seu problema ;)

[]s

Olá, Alex. Os links de arquivo de layout e activity deram 404.

Opa Antonio, acabei de ver aqui, é que é um projeto pessoal meu e tinha deixado privado, coloquei em público agora

Olá, Alex, desculpe pela demora no feedback. O app deu certo. Eu resolvi deletando a seguinte linha no style.xml:

<item name="android:windowBackground">#ffffff</item>

Agora só falta o meu colega abrir o aplicativo ao público para eu ver como será a performance na "vida real".

Depois eu vou deixar uma postagem no linkedin e também te dá um retorno por lá.

Quero muito seguir na carreira, no início é tudo muito complicado, obviamente, mas aos poucos vou me aperfeiçoando, estou estudando muito para isso.

Muito obrigado por tudo!

solução!

Massa Antonio! Fico feliz que conseguiu resolver! Eu que agradeço pela paciência e pelo carinho também ^^

Sim! início é complicado para a maioria das tecnologias, mas é apenas com o treino que a gente melhora. Dado que chegou a uma conclusão, pode marcar o tópico como solucionado?