Skip to content

Instantly share code, notes, and snippets.

@51enra
Created March 30, 2020 19:46
Show Gist options
  • Save 51enra/eb228af74ec66e4f6a23e151d30ad72c to your computer and use it in GitHub Desktop.
Save 51enra/eb228af74ec66e4f6a23e151d30ad72c to your computer and use it in GitHub Desktop.

Revisions

  1. 51enra created this gist Mar 30, 2020.
    165 changes: 165 additions & 0 deletions android-viewmodel.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,165 @@
    ## Android: REST API, Repository, ViewModel, LiveData

    Changes are described starting from the status after the previous live coding (Android Restcall with Retrofit)

    Package Structure below ```de.telekom.mayo.frontend.android.mayo```:
    - ```api```: the Retrofit interfaces and the Retrofit factory that also creates the implementations of the interfaces
    - ```model```: the Java object representations (entitites) for the data structures offered via the REST API
    - ```repo```: the repositories that provide all the CRUD methods for the REST API; one per entity
    - ```profile```: user interface related classes around the user profile, e.g. activities, viewmodels, adapters

    The package structure is likely to evolve further in the course of the project.
    Move the Interface ```UserProfileApi``` into the ```api``` package.

    The changes described below make use of the Android concept of LiveData. These are wrappers around datastructures that are often used when the content of the data structures is retrieved in background tasks, e.g. from a database or via Internet access. LiveData can be "observed" in order to detect when new content is available.

    ### Create the Retrofit factory
    In the ```api``` package, create a new class ```ApiFactory```

    ```java
    /**
    * Creates API instances for performing REST calls.
    */
    public class ApiFactory {

    private static final String BACKEND_BASE_URL = "http://10.0.2.2:8080";

    private final Retrofit retrofit;

    private static ApiFactory instance;

    private ApiFactory() {
    Gson gson = new GsonBuilder()
    .setLenient()
    .create();
    retrofit = new Retrofit.Builder()
    .baseUrl(BACKEND_BASE_URL)
    .addConverterFactory(GsonConverterFactory.create(gson))
    .build();
    }

    /**
    * @return the only ApiService instance, never null
    */
    public static synchronized ApiFactory getInstance() {
    if (instance == null) {
    instance = new ApiFactory();
    }
    return instance;
    }

    /**
    * @param retrofitApiInterface defines the REST interface, must not be null
    * @param <S>
    * @return API instance for performing REST calls, never null
    */
    public <S> S createApi(Class<S> retrofitApiInterface) {
    return retrofit.create(retrofitApiInterface);
    }
    }
    ```

    ### Create the repository
    In the ```repo``` package, create the ```UserProfileRepository``` class.
    ```java
    public class UserProfileRepository {

    private static final String LOG_TAG = "UserProfileRepository";
    private static UserProfileRepository instance;
    private final UserProfileApi userProfileApi;

    private UserProfileRepository() {
    userProfileApi = ApiFactory.getInstance().createApi(UserProfileApi.class);
    }

    public static synchronized UserProfileRepository getInstance() {
    if (instance == null) {
    instance = new UserProfileRepository();
    }
    return instance;
    }

    public LiveData<UserProfile> getById(Long id) {
    final MutableLiveData<UserProfile> userProfileMutableLiveData = new MutableLiveData<>();
    Call call = userProfileApi.getById(id);
    call.enqueue(new Callback<UserProfile>() {
    @Override
    public void onResponse(Call<UserProfile> call, Response<UserProfile> response) {
    if (response.isSuccessful()) {
    Log.d(LOG_TAG, "UserProfile " + response.body());
    userProfileMutableLiveData.setValue(response.body());
    }
    }
    @Override
    public void onFailure(Call<UserProfile> call, Throwable t) {
    Log.d(LOG_TAG, "UserProfile Error:" + t);
    userProfileMutableLiveData.setValue(null);
    }
    });
    return userProfileMutableLiveData;
    }
    }
    ```

    ### Create the viewmodel
    A viewmodel is paired with an activity. Therefore we put both in the same package. The viewmodel provides the data holder that survives activity lifecycle state changes. It exposes the LiveData to the activity. In the ```profile``` package, create the ```ShowUserProfileVieModel``` class.
    ```java
    public class ShowUserProfileViewModel extends AndroidViewModel {

    private final UserProfileRepository repository = UserProfileRepository.getInstance();
    private final MutableLiveData<UserProfile> userProfile = new MutableLiveData<>();

    public ShowUserProfileViewModel(@NonNull Application application) {
    super(application);
    }

    public MutableLiveData<UserProfile> getUserProfile() {
    return userProfile;
    }

    public LiveData<UserProfile> fetchUserProfileById(Long id) {
    return repository.getById(id);
    }

    }
    ```

    ### Adjust the activity to make use of the viewmodel
    To provide the ViewModel object in the ```ShowUserProfileActivity``` class, add one dependencies to build.gradle (app module): ```implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'```.
    The ```ShowUserProfileActivity``` class is in the ```profile``` package.
    ``` java
    public class ShowUserProfileActivity extends AppCompatActivity {

    private static final String LOG_TAG = "ShowUserProfileActivity";
    private ShowUserProfileViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_show_user_profile);
    Log.d(LOG_TAG, "onCreate, activity=" + this);
    viewModel = new ViewModelProvider(this).get(ShowUserProfileViewModel.class);
    viewModel.getUserProfile().observe(this, new Observer<UserProfile>() {
    @Override
    public void onChanged(UserProfile userProfile) {
    if (userProfile != null) {
    TextView tFirstName = findViewById(R.id.text_first_name);
    TextView tLastName = findViewById(R.id.text_last_name);
    tFirstName.setText(userProfile.getFirstName());
    tLastName.setText(userProfile.getLastName());
    }
    }
    });
    Log.d(LOG_TAG, "onCreate, viewModel=" + viewModel);
    }

    public void loadUserProfile(View view) {
    viewModel.fetchUserProfileById(1L).observe(this, new Observer<UserProfile>() {
    @Override
    public void onChanged(UserProfile userProfile) {
    viewModel.getUserProfile().setValue(userProfile);
    }
    });
    }
    }
    ```