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.

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

/**
 * 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.

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.

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.

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);
            }
        });
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment