2
votes

Comment lier Retrofit et Repository / ViewModel pour un modèle MVVM?

J'ai du mal à associer Retrofit à l'architecture MVVM. En effet, après avoir lu la documentation où ils ne parlent que de Room qui est pour la base de données locale SQLite, j'ai cherché la même chose mais pour les données qui proviennent d'un Rest Server. Donc, j'ai essayé de faire quelque chose de similaire et cela n'a pas fonctionné: https://proandroiddev.com/mvvm-architecture-viewmodel- and-livingata-part-1-604f50cda1

J'ai une activité qui observe un ViewModel:

Code d'activité:

public MutableLiveData<List<FlightPlan>> getFlightPlans() {
    Log.d(TAG, "GET_FLIGHT_PLANS");

    final MutableLiveData<List<FlightPlan>> data = new MutableLiveData<>();

    mRestApi.getFlightPlanList().enqueue(new Callback<List<FlightPlan>>() {
        @Override
        public void onResponse(Call<List<FlightPlan>> call, Response<List<FlightPlan>> response) {
            if (response.code() == 200) {
                List<FlightPlan> temp = response.body();
                for (FlightPlan flightPlan : temp) {
                    Log.d(TAG + "res", flightPlan.toString());
                }
                data.setValue(response.body());
                Log.d(TAG + "res", response.toString());
            }
        }

        @Override
        public void onFailure(Call<List<FlightPlan>> call, Throwable t) {
            List<FlightPlan> flightPlans = new ArrayList<>();
            flightPlans.add(new FlightPlan(0, "Test", 3.551, 50.52, 3.55122, 50.52625));
            data.setValue(flightPlans);
            Log.d(TAG, t.getMessage());
        }
    });

    return data;
}

Le ViewModel:

public interface RestApi {
    @GET("/plan/list")
    Call<List<FlightPlan>> getFlightPlanList();
}

Le ViewModel répond au référentiel qui utilise un motif singleton:

public class RestDao {
    private static final String BASE_URL = "http://192.168.1.78:8080";
    private static Retrofit instance;

    private static Retrofit getInstance() {
        if (instance == null) {
            instance = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return instance;
    }

    public static RestApi getRestDao() {
        return getInstance().create(RestApi.class);
    }
}

Le référentiel utilise une instance Retrofit :

public class FlightPlanRepository {
    private static final String TAG = "FlightPlanRepository";

    private static FlightPlanRepository instance;
    private RestApi mRestApi;

    private FlightPlanRepository() {
        Log.d(TAG, "CONSTRUCTOR");

        mRestApi = RestDao.getRestDao();
    }

    public static FlightPlanRepository getInstance() {
        Log.d(TAG, "GET_INSTANCE");

        if (instance == null) {
            instance = new FlightPlanRepository();
        }
        return instance;
    }

    public MutableLiveData<List<FlightPlan>> getFlightPlans() {
        Log.d(TAG, "GET_FLIGHT_PLANS");

        final MutableLiveData<List<FlightPlan>> data = new MutableLiveData<>();

        mRestApi.getFlightPlanList().enqueue(new Callback<List<FlightPlan>>() {
            @Override
            public void onResponse(Call<List<FlightPlan>> call, Response<List<FlightPlan>> response) {
                if (response.code() == 200) {
                    List<FlightPlan> temp = response.body();
                    for (FlightPlan flightPlan : temp) {
                        Log.d(TAG + "res", flightPlan.toString());
                    }
                    data.setValue(response.body());
                    Log.d(TAG + "res", response.toString());
                }
            }

            @Override
            public void onFailure(Call<List<FlightPlan>> call, Throwable t) {
                List<FlightPlan> flightPlans = new ArrayList<>();
                flightPlans.add(new FlightPlan(0, "Test", 3.551, 50.52, 3.55122, 50.52625));
                data.setValue(flightPlans);
                Log.d(TAG, t.getMessage());
            }
        });

        return data;
    }
}

Il utilise cette interface:

public class FlightPlanViewModel extends AndroidViewModel {
    private static final String TAG = "FlightPlanViewModel";

    private LiveData<List<FlightPlan>> mFlightPlans;
    private FlightPlanRepository mFlightPlanRepository;

    public FlightPlanViewModel(@NonNull Application application) {
        super(application);
        Log.d(TAG, "CONSTRUCTOR");

        mFlightPlanRepository = FlightPlanRepository.getInstance();
        mFlightPlans = mFlightPlanRepository.getFlightPlans();
    }

    public LiveData<List<FlightPlan>> getFlightPlans() {
        Log.d(TAG, "GET_FLIGHT_PLANS");

        return mFlightPlans;
    }
}

La partie du code qui ne fonctionne pas est: p>

mFlightPlanViewModel = ViewModelProviders.of(this).get(FlightPlanViewModel.class);
        mFlightPlanViewModel.getFlightPlans().observe(this, (flightPlans) -> {
            Log.d(TAG, "ON_CHANGED");

            mFlightPlanAdapter.setFlightPlans(flightPlans);
        });

Ceci renvoie une liste nulle. Je pense que je comprends pourquoi: l'appel de la méthode enqueue () a fait une demande qui est dans un autre thread, donc ici nous renvoyons des données sans attendre le résultat.

Ma question est donc de savoir comment lier Retrofit et mon ViewModel ?


0 commentaires

3 Réponses :


0
votes

Hey Kevin, juste une modification mineure de votre code. Au lieu de MutableLiveData, retournez LiveData depuis votre dépôt:

public LiveData<List<FlightPlan>> getFlightPlans() {
    Log.d(TAG, "GET_FLIGHT_PLANS");

    final MutableLiveData<List<FlightPlan>> data = new MutableLiveData<>();

    mRestApi.getFlightPlanList().enqueue(new Callback<List<FlightPlan>>() {
        @Override
        public void onResponse(Call<List<FlightPlan>> call, Response<List<FlightPlan>> response) {
            if (response.code() == 200) {
                List<FlightPlan> temp = response.body();
                for (FlightPlan flightPlan : temp) {
                    Log.d(TAG + "res", flightPlan.toString());
                }
                data.postValue(response.body());
                Log.d(TAG + "res", response.toString());
            }
        }

        @Override
        public void onFailure(Call<List<FlightPlan>> call, Throwable t) {
            List<FlightPlan> flightPlans = new ArrayList<>();
            flightPlans.add(new FlightPlan(0, "Test", 3.551, 50.52, 3.55122, 50.52625));
            data.postValue(flightPlans);
            Log.d(TAG, t.getMessage());
        }
    });

    return data;
}


4 commentaires

Merci beaucoup pour votre réponse rapide. J'ai essayé ce que vous m'avez dit mais cela renvoie toujours une liste nulle. C'est bizarre ...


Obtenez-vous une réponse dans votre API?


Lorsque je démarre mon application, elle se bloque car je donne la réponse à un adaptateur de vue recycleur. Mais si j'ajoute une entrée par défaut dans mon code, il n'affiche que la valeur par défaut.


Pourriez-vous également publier votre code d'activité. Vérifiez également si votre API vous renvoie la bonne liste de données



0
votes

Si je fais cela:

public class FlightPlanActivity extends AppCompatActivity implements View.OnClickListener, SwipeRefreshLayout.OnRefreshListener, LifecycleOwner {
    private static final String TAG = "FlightPlanActivity";

    private FlightPlanAdapter mFlightPlanAdapter;
    private FlightPlanViewModel mFlightPlanViewModel;

    /**
     * UI
     */
    private FloatingActionButton mAddPlanButton;
    private RecyclerView mRecyclerView;
    private SwipeRefreshLayout mSwipeRefreshLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.activity_flight_plan);

        Log.d(TAG, "onCreate called");

        initUI();
    }

    private void initUI() {
        this.setTitle("Flight Plans");

        mRecyclerView = findViewById(R.id.flight_plan_list);
        mAddPlanButton = findViewById(R.id.add_flight_plan);
        mSwipeRefreshLayout = findViewById(R.id.refresh_flight_plan_list);

        mAddPlanButton.setOnClickListener(this);
        mSwipeRefreshLayout.setOnRefreshListener(this);

        mFlightPlanAdapter = new FlightPlanAdapter();

        mRecyclerView.setHasFixedSize(true);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRecyclerView.setAdapter(mFlightPlanAdapter);

        mFlightPlanViewModel = ViewModelProviders.of(this).get(FlightPlanViewModel.class);
        mFlightPlanViewModel.getFlightPlans().observe(this, (flightPlans) -> {
            Log.d(TAG, "ON_CHANGED");

            mFlightPlanAdapter.setFlightPlans(flightPlans);
        });
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == mAddPlanButton.getId()) {
            // TODO: A implémenter
        }
    }

    @Override
    public void onRefresh() {
        mFlightPlanAdapter.setFlightPlans(mFlightPlanViewModel.getFlightPlans().getValue());

        mSwipeRefreshLayout.setRefreshing(false);
    }
}

Je peux voir dans le Logcat la valeur par défaut, et deux valeurs qui proviennent de mon serveur de repos mais les chaînes sont des valeurs nulles et int / doubles sont à 0 et il n'ajoute pas les deux entrées restantes à la vue du recycleur.

Voici mon code d'activité:

public LiveData<List<FlightPlan>> getFlightPlans() {
    Log.d(TAG, "GET_FLIGHT_PLANS");

    final MutableLiveData<List<FlightPlan>> data = new MutableLiveData<>();


    List<FlightPlan> flightPlans = new ArrayList<>();
    flightPlans.add(new FlightPlan(5, "Test5", 3.551, 50.52, 3.55122, 50.52625));
    data.setValue(flightPlans);
    Log.d(TAG, data.getValue().toString());


    mRestApi.getFlightPlanList().enqueue(new Callback<List<FlightPlan>>() {
        @Override
        public void onResponse(Call<List<FlightPlan>> call, Response<List<FlightPlan>> response) {
            if (response.code() == 200) {
                List<FlightPlan> temp = response.body();
                for (FlightPlan flightPlan : temp) {
                    Log.d(TAG + "res", flightPlan.toString());
                }
                data.setValue(response.body());
                Log.d(TAG + "res", response.toString());
            }
        }

        @Override
        public void onFailure(Call<List<FlightPlan>> call, Throwable t) {
            List<FlightPlan> flightPlans = new ArrayList<>();
            flightPlans.add(new FlightPlan(0, "Test", 3.551, 50.52, 3.55122, 50.52625));
            data.setValue(flightPlans);
            Log.d(TAG, t.getMessage());
        }
    });

    return data;
}


6 commentaires

Ainsi, vous n'obtenez votre valeur par défaut que dans recyclerview. Les valeurs de votre API de repos sont nulles et égales à 0. Est-ce que c'est tout?


Vérifiez dans votre observateur d'activités si la taille de la liste qu'il obtient est égale à la taille de votre réponse api


J'obtiens ceci dans logcat: 2019-04-27 23: 18: 41.579 26214-26214 / fr.kevin.myapplication D / FlightPlanRepository: [FlightPlan {mId = 5, mName = 'Test5', mLat1 = 3.551, mLon1 = 50.52, mLat2 = 3.55122, mLon2 = 50.52625}] 2019-04-27 23: 18: 41.799 26214-26214 / fr.kevin.myapplication D / FlightPlanRepositoryres: FlightPlan {mId = 0, mName = 'null', mLat1 = 0.0, mLon1 = 0.0, mLat2 = 0.0, mLon2 = 0.0} 2019-04-27 23: 18: 41.799 26214-26214 / fr.kevin.myapplication D / FlightPlanRepositoryres: FlightPlan {mId = 0, mName = 'null', mLat1 = 0.0, mLon1 = 0,0, mLat2 = 0,0, mLon2 = 0,0} et la taille est 1.


J'ai trouvé cette vidéo suivante youtube.com/results?search_query=retrofit+viewmodel où il existe une implémentation pour consommer un serveur de repos via le modèle mvvm. Il récupère les données dans la vue et les stocke à l'aide de la méthode insert () du ViewModel. Il lie le viewModel à une base de données sqlite pour la mise en cache. Il semble que si je veux obtenir mes données de repos, je dois le faire comme ça.


Ce n'est pas nécessaire de le faire. Vous pouvez directement remplir votre vue avec vos données de repos. Je ne pensais pas que vous aviez quelque chose de mal dans votre code aussi.


Nous pouvons effectuer des tâches asynchrones et synchrones avec la modernisation. Ici, je fais une tâche asynchrone pour que la méthode n'attende pas le résultat et renvoie une valeur nulle. Je peux essayer une demande synchrone mais dans ce cas, je dois faire l'appel dans l'activité avec une tâche asynchrone je pense.



0
votes

Alors, que se passe-t-il quand il y a une réponse si je fais ça?

public class UserRepository {
    private Webservice webservice;
    // ...
    public LiveData<User> getUser(int userId) {
        // This isn't an optimal implementation. We'll fix it later.
        final MutableLiveData<User> data = new MutableLiveData<>();
        webservice.getUser(userId).enqueue(new Callback<User>() {
            @Override
            public void onResponse(Call<User> call, Response<User> response) {
                data.setValue(response.body());
            }

            // Error case is left out for brevity.
        });
        return data;
    }
}

Si vous exécutez ça, il y a d'abord un objet nul retourné mais s'il y a une réponse, il retournera quelque chose?

J'ai essayé ça:

public LiveData<List<FlightPlan>> getFlightPlans() {
    Log.d(TAG, "GET_FLIGHT_PLANS");

    MutableLiveData<List<FlightPlan>> data = new MutableLiveData<>();
    List<FlightPlan> flightPlans;

    try {
        Response<List<FlightPlan>> response = mRestApi.getFlightPlanList().execute();
        if (response.isSuccessful()) {
            flightPlans = response.body();
        } else {
            Log.d(TAG, "Can't get data !");
            throw new Exception("Can't get data !");
        }
    } catch (Exception e) {
        e.printStackTrace();

        flightPlans = new ArrayList<>();
        flightPlans.add(new FlightPlan(5, "Test5", 3.551, 50.52, 3.55122, 50.52625));
    }

    data.setValue(flightPlans);
    Log.d(TAG, data.getValue().toString());

    return data;
}

Maintenant, il plante sur la ligne d'exécution parce que je lance ça dans le thread principal .. Donc je dois faire une tâche asynchrone mais où?

Autre option: Faire une tâche asynchrone comme au début, mettre un setter sur l'objet de la vue modèle et quand il y a une réponse j'appelle le setter .. Est ça fait du bien?

Merci pour les réponses que vous m'avez données! Cela a été très utile.

EDIT: Pourquoi y a-t-il ce code sur la documentation Google:

public LiveData<List<FlightPlan>> getFlightPlans() {
    Log.d(TAG, "GET_FLIGHT_PLANS");

    MutableLiveData<List<FlightPlan>> data = new MutableLiveData<>();

    mRestApi.getFlightPlanList().enqueue(new Callback<List<FlightPlan>>() {
        @Override
        public void onResponse(Call<List<FlightPlan>> call, Response<List<FlightPlan>> response) {
            if (response.code() == 200) {
                data.setValue(response.body());
            }
        }

        @Override
        public void onFailure(Call<List<FlightPlan>> call, Throwable t) {
            // Do something
        }
    });

    return data;
}

Cela signifie que vous pouvez faire une telle chose? p>

SOLUTION!

MODIFIER:

Comment connecter ViewModel avec Repository afin que les données soient propagées vers la vue (MVVM, Livedata)

Ceci m'aide beaucoup !!!

J'ai trouvé la solution! Je suis stupide: j'insère d'anciennes dépendances dans le fichier gradle!


0 commentaires