1
votes

Évitez le double appel API à partir de la navigation en bas, appuyez rapidement sur

J'ai développé une application Android à l'aide de Kotlin et toute la structure et les fonctionnalités sont complètes, mais j'ai remarqué un petit problème lorsque je tape rapidement à plusieurs reprises, au moins deux fois sur un bouton qui fait un appel API.

Pour les appels API J'utilise une combinaison de RetroFit2 et GsonConverterFactory. L'appel est le suivant:

java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.ImageView.setImageResource(int)' on a null object reference`

J'ai un peu modifié le code pour éviter des noms de variables spécifiques

Donc, comme mentionné avant que ce code fonctionne correctement, le problème survient lorsque je clique deux fois rapidement sur le bouton de navigation. D'après ce que je comprends, il essaie de faire un autre appel d'API avant que l'actuel n'ait répondu et n'obtienne une réponse nulle, alors j'essaie essentiellement de remplacer une image par une ressource nulle et cela me montre cette erreur:

    fun fetchInfo(id: Int) {
        val retrofit = Retrofit.Builder()
            .baseUrl("https://www.mysitesurl.com/api/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()

        val api = retrofit.create(ApiService::class.java)

        api.getInfo(id).enqueue(object: Callback<DataType> {

            override fun onResponse(call: Call<DataType>, response: Response<DataType>) {
                var resp = response.body()!!
                my_image.setImageResource(resources.getIdentifier(resp.image, "drawable", context!!.packageName))
                my_image.visibility = View.VISIBLE

                my_label.text = resp.text
                my_label.visibility = View.VISIBLE
            }

            override fun onFailure(call: Call<FechaDia>, t: Throwable) {

            }

        })
    }

J'ai essayé d'utiliser try / catch mais il effectue toujours l'appel et reçoit toujours une requête nulle. Y a-t-il un moyen d'empêcher que cela se produise ou que me manque-t-il dans mon processus ici?

Le problème principal est qu'il ne montre pas qu'une erreur, l'application se ferme et affiche l'application a arrêté. Ouvrez à nouveau l'application message.


5 commentaires

Vous essayez de forcer une response.body () !! Nullable à être non NULL alors essayez de faire quelque chose comme response.body ()?. .


Juste pour confirmer @RodrigoQueiroz, si je fais cela, tous les setters de l'interface utilisateur ne fonctionneraient que lorsque la réponse n'est pas nulle? Sinon ça ne ferait rien?


En effet, rien ne serait défini à moins d'avoir un corps réel! Je ne pense pas que l'utilisation d'un drapeau soit une solution viable, donc s'il y avait plus de code ou de contexte, peut-être qu'une meilleure solution aurait pu être proposée!


@RodrigoQueiroz votre solution a fait l'affaire sans trop modifier le code, vous pouvez le mettre en réponse :)


Encore une chose, cela fonctionne bien pour les fragments réguliers, mais parfois l'application se ferme toujours lorsque j'utilise des adaptateurs. Ce que j'ai fait, c'est que j'ai mis l'attribution de l'adaptateur dans le bloc d'exécution. Est-ce exact ou est-ce différent dans ce cas?


3 Réponses :


1
votes

Utilisez un indicateur global comme celui-ci:

clicked = false;

Dans onClick:

if(false){
     callApi();
     clicked = true;
}

Et dans la réponse Success ou Error, indiquez-le faux :

private boolean clicked = false;


2 commentaires

Le problème est que cela se produit avec ma navigation du bas et cela affecte le fragment principal, alors vous dites que je crée une variable globale dans mon activité principale et que je la change? Que se passe-t-il lorsqu'il existe des boutons locaux (spécifiques au fragment) qui effectuent également des appels d'API?


Faire le drapeau en activité et le changer de partout (Activité ainsi que les fragments)



0
votes

Comme mentionné dans les commentaires, essayez de ne pas forcer Nullable à un type non Nullable car il y aura des effets secondaires (Exceptions).

Idéalement, vous voudriez également découpler un peu les choses pour une meilleure lisibilité du code: p>

fun retrofit(): Retrofit = Retrofit.Builder()
    .baseUrl("https://www.mysitesurl.com/api/")
    .addConverterFactory(GsonConverterFactory.create())
    .build()

fun apiService(): ApiService = retrofit().create(ApiService::class.java)

fun fetchInfo(id: Int) =
    apiService().getInfo(id).enqueue(object : Callback<Element.DataType> {
        override fun onResponse(
            call: Call<Element.DataType>,
            response: Response<Element.DataType>
        ) {
            response.body()?.run { renderView(this) }
        }
        override fun onFailure(call: Call<FechaDia>, t: Throwable) {}
    })

fun renderView(response: DataType) = view?.apply {
    val image = resources.getIdentifier(response.image, "drawable", context.packageName))
    my_image.setImageResource(image)
    my_image.visibility = View.VISIBLE
    my_label.text = response.text
    my_label.visibility = View.VISIBLE
}

Si vous avez un adaptateur, vous n'avez pas à l'affecter dans le renderView car ce que vous feriez probablement est de ne mettre à jour l'adaptateur qu'une fois que vous avez récupéré les données de l'API.

Ayez l'adaptateur comme propriété dans l'activité ou dans le fragment, puis une fois que vous obtenez la réponse, appelez l'adaptateur et envoyez la liste.

Pour compléter la question car il n'y a pas grand-chose code à regarder, je suppose que si vous utilisez la navigation du bas sans utiliser la bibliothèque de navigation jetpack, vous pouvez utiliser la navigation du bas avec ViewPager et utiliser un OnNavigationItemSelectedListener sur la navigation du bas pour basculer entre les pages sur l'adaptateur ViewPager .

Et si vous aviez retentionInstance = true le fragment ne se recréerait donc pas o un seul appel à l'API aurait été effectué.


1 commentaires

Votre solution a fonctionné pour moi, je ne l'ai pas divisée autant que vous le suggérez, mais j'ai utilisé le response.body () ?. run {...} :)



0
votes

J'ai l'impression que c'est une excellente situation pour un anti-rebond.

Première référence que j'ai trouvée: Kotlin Android debounce

Je suggérerais de regarder la réponse de SANAT, cela semble être une implémentation très propre et vous aidera à gérer plusieurs clics sans avoir plusieurs incendies de fonctions.


0 commentaires