Inspired this guide: https://developer.android.com/jetpack/docs/guide I try to build some app with similar architecture.
App interact with remote API via http protocol (json responses from server). For example, GET /bonuses/:id
return {status: true, bonus: {id: 1, name: "test bonus"}}
POST /login
can return {status: true, token: "dafadafadfadfadf"}
or {status: false, message: "Password or phone incorrect"
}
For example, I post here my LoginFragment, LoginViewModel, UserRepository and RemoteDataSource.
Does my MVVM approach good? What is very bad in my code and how I can improve bad places? Possible memory leaks, etc...
One bad thing, what I see - repeating code in UserRepository, how I can improve it?
Other issue - naming some instance variables with m and other - without (I fix it later).
What is good in my code?
public class LoginFragment extends Fragment implements View.OnClickListener {
public static String TAG = "LoginFragment";
private NavController mNavController;
private ApiService mApiService;
@BindView(R.id.btn_login)
Button mBtnLogin;
@BindView(R.id.et_phone)
EditText mEtPhone;
@BindView(R.id.et_password)
EditText mEtPassword;
@BindView(R.id.tv_forgot_password)
TextView mTvForgotPassword;
@BindView(R.id.tv_registration)
TextView mTvRegistration;
MainActivity mActivity;
private LoginViewModel mLoginViewModel;
public LoginFragment() {
// Required empty public constructor
}
public static LoginFragment newInstance(String param1, String param2) {
LoginFragment fragment = new LoginFragment();
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.fragment_login, container, false);
ButterKnife.bind(this, v);
mActivity = (MainActivity) getActivity();
mNavController = mActivity.getNavController();
mActivity.getSupportActionBar().setTitle(getString(R.string.login));
mActivity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mActivity.getSupportActionBar().setDisplayUseLogoEnabled(false);
mApiService = RetrofitClient.getInstance().getApiService();
mBtnLogin.setOnClickListener(this);
mTvForgotPassword.setOnClickListener(this);
mTvRegistration.setOnClickListener(this);
return v;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
super.onActivityCreated(savedInstanceState);
mLoginViewModel = ViewModelProviders.of(this).get(LoginViewModel.class);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_login: {
mLoginViewModel.init(mEtPhone.getText().toString().replaceAll("\\D+", ""), mEtPassword.getText().toString());
mLoginViewModel.getNetworkState().observe(this, new Observer<Integer>() {
@Override
public void onChanged(@Nullable Integer state) {
if(state == NetworkState.LOADED) {
Boolean status = mLoginViewModel.getRemoteDataSource().getStatus();
if(status) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
SharedPreferences.Editor editor = preferences.edit();
editor.putString(User.TOKEN_NAME, mLoginViewModel.getToken());
editor.apply();
mNavController.navigate(R.id.action_loginFragment_to_bonusesFragment);
} else {
AlertDialogHelper.showDialog(mActivity, getString(R.string.warning), mLoginViewModel.getRemoteDataSource().getMessage());
}
} else if(state == NetworkState.FAILED) {
AlertDialogHelper.showDialog(mActivity, getString(R.string.warning), mLoginViewModel.getRemoteDataSource().getMessage());
}
}
});
break;
}
case R.id.tv_registration: {
mNavController.navigate(R.id.action_loginFragment_to_registrationFragment);
break;
}
case R.id.tv_forgot_password: {
mNavController.navigate(R.id.action_loginFragment_to_restorePasswordFragment);
break;
}
}
}
}
LoginViewModel:
public class LoginViewModel extends ViewModel {
private UserRepository mUserRepository;
private RemoteDataSource<String> mLogin;
private String mPhone;
private String mPassword;
public LoginViewModel() {
}
public void init(String phone, String password) {
mUserRepository = new UserRepository();
mPassword = password;
mPhone = phone;
mLogin = mUserRepository.login(phone, password);
}
public LiveData<Integer> getNetworkState() {
return mLogin.getNetworkState();
}
public Boolean getStatus() {
return mLogin.getStatus();
}
public String getToken() {
return mLogin.getData();
}
public RemoteDataSource<String> getRemoteDataSource() {
return mLogin;
}
}
UserRepository:
public class UserRepository {
public static String TAG = "UserRepository";
ApiService mApiService;
SharedPreferences mPrefs;
Context mContext;
RemoteDataSource<User> mUserInfo;
RemoteDataSource<String> mUserLogin;
RemoteDataSource<String> mUserSendCode;
public UserRepository() {
mApiService = new RetrofitClient().getApiService();
mContext = App.getAppContext();
mPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
mUserInfo = new RemoteDataSource<>();
mUserLogin = new RemoteDataSource<>();
mUserSendCode = new RemoteDataSource<>();
}
public RemoteDataSource getUserInfo() {
mUserInfo.setIsLoading();
Call<ApiResponse> userCall = mApiService.getUserInfo(mPrefs.getString(User.TOKEN_NAME, null));
userCall.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {
mUserInfo.setIsLoaded(response.body().getUser());
mUserInfo.setStatus(response.body().getStatus());
mUserInfo.setMessage(response.body().getMessage());
}
@Override
public void onFailure(Call<ApiResponse> call, Throwable t) {
Log.e(TAG, t.getMessage());
mUserInfo.setFailed(t.getMessage());
}
});
return mUserInfo;
}
public RemoteDataSource login(String phone, String password) {
mUserLogin.setIsLoading();
Call<ApiResponse> loginCall = mApiService.login(phone, password);
loginCall.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {
mUserLogin.setIsLoaded(response.body().getToken());
mUserLogin.setStatus(response.body().getStatus());
mUserLogin.setMessage(response.body().getMessage());
}
@Override
public void onFailure(Call<ApiResponse> call, Throwable t) {
Log.e(TAG, t.getMessage());
mUserLogin.setFailed(t.getMessage());
}
});
return mUserLogin;
}
public RemoteDataSource sendCode(String phone) {
mUserSendCode.setIsLoading();
Call<ApiResponse> sendCodeCall = mApiService.sendCode(phone);
sendCodeCall.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(Call<ApiResponse> call, Response<ApiResponse> response) {
mUserSendCode.setIsLoaded(response.body().getToken());
mUserSendCode.setStatus(response.body().getStatus());
mUserSendCode.setMessage(response.body().getMessage());
}
@Override
public void onFailure(Call<ApiResponse> call, Throwable t) {
Log.e(TAG, t.getMessage());
mUserLogin.setFailed(t.getMessage());
}
});
return mUserSendCode;
}
}
and RemoteDataSource:
public class RemoteDataSource<T> {
private final MutableLiveData<Integer> networkState = new MutableLiveData<>();
private T data;
private String message;
private Boolean status;
private String code;
public RemoteDataSource() {
networkState.postValue(NetworkState.DEFAULT);
}
public MutableLiveData<Integer> getNetworkState() {
return networkState;
}
public void setIsLoading() {
networkState.postValue(NetworkState.LOADING);
}
public void setDefault() {
networkState.postValue(NetworkState.DEFAULT);
}
public void setIsLoaded(T data) {
networkState.postValue(NetworkState.LOADED);
this.data = data;
}
public void setFailed(@NonNull String errorMessage) {
this.message = errorMessage;
networkState.postValue(NetworkState.FAILED);
}
public void setMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public Boolean getStatus() {
return status;
}
public void setStatus(Boolean status) {
this.status = status;
}
public T getData() {
return data;
}
public String getCode() { return code;}
public void setCode(String code) {this.code = code;}
}
User - is simple pojo model:
public class User {
public static String TOKEN_NAME="token";
private String token;
private Integer bonus;
private String name;
private String email;
private String phone;
public Integer getBonus() { return bonus;}
public void setBonus(Integer bonus) {this.bonus = bonus;}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getName() {
return name;
}
public String getPhone() {
return phone;
}
public String getEmail() {
return email;
}
}
BonusRepository, BonusVieModel, Bonus model and BonusFragment looks like the very similar on upper code.
UPD: it will be great, if you answer cover, how I organize repository/mvvm patterns.