web-dev-qa-db-fra.com

La meilleure pratique pour mettre en œuvre le rappel de modification à une activité recréée?

Je passe à Retrofit et j'essaie de comprendre l'architecture appropriée pour l'utiliser avec des rappels asynchrones.

Par exemple j'ai une interface:

interface RESTService{
    @GET("/api/getusername")
    void getUserName(@Query("user_id") String userId, 
                     Callback<Response> callback);
}

Et je lance ceci depuis l'activité principale:

RestAdapter restAdapter = new RestAdapter.Builder()
        .setServer("WEBSITE_URL")     
        .build();
RESTService api = restAdapter.create(RESTService.class);
api.getUserName(userId, new Callback<Response> {...});

Ensuite, l'utilisateur fait pivoter l'appareil et j'ai créé une nouvelle activité ... Que s'est-il passé ici? Comment puis-je obtenir une réponse à la nouvelle activité (je suppose que l'appel de l'API en arrière-plan s'exécutera plus longtemps que la première vie de l'activité). Peut-être que je dois utiliser une instance statique de rappel ou quoi? S'il te plait montre moi le bon chemin ...

72
lordmegamax

Utilisez otto . Par exemple, il existe de nombreux échantillons à mélanger avec otto et retrofit https://github.com/pat-dalberg/ImageNom/blob/master/src/com/dalberg/Android/imagenom/async/FlickrClient. Java

Ou lisez cet article http://www.mdswanson.com/blog/2014/04/07/durable-Android-rest-clients.html Il répond à presque toutes les questions

42
avgx

Pour les appels de serveur potentiellement longs, j'utilise un AsyncTaskLoader . Pour moi, le principal avantage des chargeurs est la gestion du cycle de vie des activités. onLoadFinished n'est appelé que si votre activité est visible pour l'utilisateur. Les chargeurs sont également partagés entre les changements d’activité/de fragment et d’orientation.

J'ai donc créé un ApiLoader qui utilise des appels synchrones postérieurs dans loadInBackground .

abstract public class ApiLoader<Type> extends AsyncTaskLoader<ApiResponse<Type>> {

    protected ApiService service;
    protected ApiResponse<Type> response;

    public ApiLoader(Context context) {
        super(context);
        Vibes app = (Vibes) context.getApplicationContext();
        service = app.getApiService();
    }

    @Override
    public ApiResponse<Type> loadInBackground() {
        ApiResponse<Type> localResponse = new ApiResponse<Type>();

        try {
            localResponse.setResult(callServerInBackground(service));
        } catch(Exception e) {
            localResponse.setError(e);
        }

        response = localResponse;
        return response;
    }

    @Override
    protected void onStartLoading() {
        super.onStartLoading();
        if(response != null) {
            deliverResult(response);
        }

        if(takeContentChanged() || response == null) {
            forceLoad();
        }
    }

    @Override
    protected void onReset() {
        super.onReset();
        response = null;
    }


    abstract protected Type callServerInBackground(SecondLevelApiService api) throws Exception;

}

Dans votre activité, vous initiez ce chargeur comme ceci:

getSupportLoaderManager().initLoader(1, null, new LoaderManager.LoaderCallbacks<ApiResponse<DAO>>() {
        @Override
        public Loader<ApiResponse<DAO>> onCreateLoader(int id, Bundle args) {
            spbProgress.setVisibility(View.VISIBLE);

            return new ApiLoader<DAO>(getApplicationContext()) {
                @Override
                protected DAO callServerInBackground(ApiService api) throws Exception {
                    return api.requestDAO();
                }
            };
        }

        @Override
        public void onLoadFinished(Loader<ApiResponse<DAO>> loader, ApiResponse<DAO> data) {
            if (!data.hasError()) {
                DAO dao = data.getResult();
                //handle data
            } else {
                Exception error = data.getError();
                //handle error
            }
        }

        @Override
        public void onLoaderReset(Loader<ApiResponse<DAO>> loader) {}
    });

Si vous souhaitez demander plusieurs fois des données, utilisez restartLoader au lieu de initLoader .

34
Benjamin

J'utilise une sorte d'implémentation MVP (ModelViewPresenter) sur mon Android apps. Pour la demande de modification, j'ai effectué les appels d'activité à son présentateur respectif, ce qui permet à son tour la demande de modification et paramètre J'envoie un rappel avec un écouteur personnalisé associé (implémenté par le présentateur). Lorsque le rappel atteint onSuccess ou onFailure méthodes, j'appelle les méthodes respectives de l'écouteur, qui appelle le présentateur, puis les méthodes d'activité: P

Maintenant, au cas où l'écran est allumé, lorsque mon activité est recréée, elle s'attache à l'animateur. Ceci est réalisé en utilisant une implémentation personnalisée de l'application Android, où il conserve l'instance des présentateurs, et en utilisant une carte pour récupérer le présentateur correct en fonction de la classe de l'activité.

Je ne sais pas si c'est la meilleure façon, peut-être que la réponse @pareshgoel est meilleure, mais cela a fonctionné pour moi: D

Exemples:

public abstract interface RequestListener<T> {

    void onSuccess(T response);

    void onFailure(RetrofitError error);
}

...

public class RequestCallback<T> implements Callback<T> {

    protected RequestListener<T> listener;

    public RequestCallback(RequestListener<T> listener){
        this.listener = listener;
    }

    @Override
    public void failure(RetrofitError arg0){
        this.listener.onFailure(arg0);
    }

    @Override
    public void success(T arg0, Response arg1){
        this.listener.onSuccess(arg0);
    }

}

Implémentez l'écouteur quelque part sur le présentateur, et sur les méthodes de substitution, appelez la méthode du présentateur qui effectuera l'appel à l'activité. Et appelez où vous voulez sur le présentateur pour tout initier: P

Request rsqt = restAdapter.create(Request.class);
rsqt.get(new RequestCallback<YourExpectedObject>(listener));

J'espère que ça vous aide.

21
LeoFarage

Tout d’abord, votre activité se perd ici parce que cette ligne: api.getUserName (userId, new Callback {...}) crée une classe de rappel anonyme qui contient une référence forte à votre compte MainActivity. Lorsque le périphérique est pivoté avant que le rappel ne soit appelé, MainActivity ne sera pas nettoyé. En fonction de ce que vous faites dans Callback.call (), votre application peut générer un comportement non défini.

L'idée générale pour gérer de tels scénarios est la suivante:

  1. Ne créez jamais une classe interne non statique (ou une classe anonyme, comme indiqué dans le problème).
  2. Au lieu de cela, créez une classe statique qui contient un WeakReference <> dans l'activité/le fragment.

Ce qui précède empêche simplement les fuites. Cela ne vous aide toujours pas à faire rappeler le rappel de votre activité à votre activité.

Maintenant, pour récupérer les résultats sur votre composant (Activity dans votre cas) même après le changement de configuration, vous pouvez utiliser un fragment conservé sans tête, attaché à votre Activity, qui appelle la modification. En savoir plus sur les fragments conservés - http://developer.Android.com/reference/Android/app/Fragment.html#setRetainInstance (boolean)

L'idée générale est que le fragment s'attache automatiquement à l'activité lors du changement de configuration.

10
pareshgoel

Je vous recommande fortement regardez cette vidéo sur Google I/O .

Il explique comment créer REST des requêtes en les déléguant à un service (qui n'est presque jamais tué). Lorsque la requête est terminée, elle est immédiatement stockée dans la base de données intégrée d'Android afin que les données soient stockées. disponible immédiatement lorsque votre activité est prête.

Avec cette approche, vous ne devez jamais vous inquiéter du cycle de vie de l'activité et vos demandes sont traitées de manière beaucoup plus découplée.

La vidéo ne parle pas spécifiquement de la modernisation, mais vous pouvez facilement adapter la modification à ce paradigme.

5
Martin Konecny

Utilisation de Retrofit2 pour gérer le changement d’orientation. On m'a posé cette question lors d'un entretien d'embauche et j'ai été rejeté pour ne pas le savoir à l'époque, mais le voici maintenant.

public class TestActivity extends AppCompatActivity {
Call<Object> mCall;
@Override
    public void onDestroy() {
        super.onDestroy();
        if (mCall != null) {
            if (mCall.isExecuted()) {
                //An attempt will be made to cancel in-flight calls, and
                // if the call has not yet been executed it never will be.
                mCall.cancel();
            }
        }
    }
    }
3
John61590

Quand j'étais débutant dans la rénovation, j'ai appris la meilleure façon de ces sites

Consommer api avec retrofit

tutoriel de rattrapage

2
Ameen Maheen

Utilisez Robospice

Tous les composants de votre application qui nécessitent des données sont enregistrés auprès du service Spice. Le service prend en charge l’envoi de votre demande au serveur (via la modification si vous le souhaitez). Lorsque la réponse est renvoyée, tous les composants enregistrés sont notifiés. S'il y en a une qui n'est plus disponible (comme une activité qui a été lancée à cause de la rotation), elle n'est tout simplement pas notifiée.

Avantage: une seule demande qui ne soit pas perdue, que vous fassiez pivoter votre appareil, ouvrir de nouveaux dialogues/fragments, etc.

2
stoefln