问题
I try to implement MVVM by Mindorks to show data from here :(https://api.themoviedb.org/3/movie/384018?api_key=67bc513a7a353631119fdffe5f7377a8&language=en-US) in my Activity. I try using databinding for update UI, everything is going well untill I try to rotate my screen, I see from my logcat the data is always reload and my ImageView is always refresh, this is my Activity:
public class DetailActivity extends BaseActivity<ActivityDetailBinding, DetailViewModel> implements DetailNavigator {
@Inject
ViewModelProviderFactory factory;
private DetailViewModel detailViewModel;
public static final String INTENT_ID = "id_intent";
public static final String INTENT_FLAG = "id_flag";
private ActivityDetailBinding mActivityDetailBinding;
public static Intent newIntent(Context context) {
return new Intent(context, DetailActivity.class);
}
@Override
public int getBindingVariable() {
return BR.viewModel;
}
@Override
public int getLayoutId() {
return R.layout.activity_detail;
}
@Override
public DetailViewModel getViewModel() {
detailViewModel = ViewModelProviders.of(this, factory).get(DetailViewModel.class);
return detailViewModel;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
detailViewModel.setNavigator(this);
mActivityDetailBinding = getViewDataBinding();
initView();
initData(savedInstanceState);
}
private void initData(Bundle savedInstanceState) {
Bundle extras = getIntent().getExtras();
if (extras != null) {
int id = extras.getInt(INTENT_ID, 0);
int flag = extras.getInt(INTENT_FLAG, 0);
detailViewModel.fetchDetail(id, flag);
}
}
private void initView() {
if (getSupportActionBar() != null) {
getSupportActionBar().hide();
}
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
@Override
public void ShowProgressDialog(Boolean loading) {
if (loading) {
showLoading();
} else {
hideLoading();
}
}
}
and my BaseActivity like this :
public abstract class BaseActivity<T extends ViewDataBinding, V extends BaseViewModel> extends AppCompatActivity
implements BaseFragment.Callback {
// TODO
// this can probably depend on isLoading variable of BaseViewModel,
// since its going to be common for all the activities
private ProgressDialog mProgressDialog;
private T mViewDataBinding;
private V mViewModel;
/**
* Override for set binding variable
*
* @return variable id
*/
public abstract int getBindingVariable();
/**
* @return layout resource id
*/
public abstract
@LayoutRes
int getLayoutId();
/**
* Override for set view model
*
* @return view model instance
*/
public abstract V getViewModel();
@Override
public void onFragmentAttached() {
}
@Override
public void onFragmentDetached(String tag) {
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
performDependencyInjection();
super.onCreate(savedInstanceState);
performDataBinding();
}
public T getViewDataBinding() {
return mViewDataBinding;
}
@TargetApi(Build.VERSION_CODES.M)
public boolean hasPermission(String permission) {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M ||
checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
}
public void hideKeyboard() {
View view = this.getCurrentFocus();
if (view != null) {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
}
public void hideLoading() {
if (mProgressDialog != null && mProgressDialog.isShowing()) {
mProgressDialog.cancel();
}
}
public void showLoading() {
hideLoading();
mProgressDialog = CommonUtils.showLoadingDialog(this);
}
public boolean isNetworkConnected() {
return NetworkUtils.isNetworkConnected(getApplicationContext());
}
public void performDependencyInjection() {
AndroidInjection.inject(this);
}
@TargetApi(Build.VERSION_CODES.M)
public void requestPermissionsSafely(String[] permissions, int requestCode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(permissions, requestCode);
}
}
// public void showLoading() {
// hideLoading();
// mProgressDialog = CommonUtils.showLoadingDialog(this);
// }
private void performDataBinding() {
mViewDataBinding = DataBindingUtil.setContentView(this, getLayoutId());
this.mViewModel = mViewModel == null ? getViewModel() : mViewModel;
mViewDataBinding.setVariable(getBindingVariable(), mViewModel);
mViewDataBinding.setLifecycleOwner(this);
mViewDataBinding.executePendingBindings();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
this is my ViewModelFactory class :
@Singleton
public class ViewModelProviderFactory extends ViewModelProvider.NewInstanceFactory {
private final DataManager dataManager;
private final SchedulerProvider schedulerProvider;
@Inject
public ViewModelProviderFactory(DataManager dataManager,
SchedulerProvider schedulerProvider) {
this.dataManager = dataManager;
this.schedulerProvider = schedulerProvider;
}
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
if (modelClass.isAssignableFrom(DetailViewModel.class)) {
return (T) new DetailViewModel(dataManager,schedulerProvider);
}
throw new IllegalArgumentException("Unknown class name");
}
}
this is my ViewModel class :
public class DetailViewModel extends BaseViewModel<DetailNavigator> {
private final ObservableField<String> originalName = new ObservableField<>();
private final ObservableField<String> releaseDate = new ObservableField<>();
private final ObservableField<String> overview = new ObservableField<>();
private final ObservableField<String> genreMovie = new ObservableField<>();
private final ObservableField<String> posterPath = new ObservableField<>();
private final ObservableField<String> voteAverage = new ObservableField<>();
public DetailViewModel(DataManager dataManager, SchedulerProvider schedulerProvider) {
super(dataManager, schedulerProvider);
}
public void fetchDetail(int id, int flag) {
if (flag == 1) {
getNavigator().ShowProgressDialog(true);
getCompositeDisposable().add(getDataManager()
.getApiHelper().doDetailMovie(id, URLConfig.API_KEY, getDataManager().getLanguage())
.subscribeOn(getSchedulerProvider().io())
.observeOn(getSchedulerProvider().ui())
.subscribe(detailResponse -> {
setUpData(detailResponse);
getNavigator().ShowProgressDialog(false);
// getNavigator().updateView();
}, throwable -> {
getNavigator().ShowProgressDialog(false);
}));
} else if (flag == 2) {
getNavigator().ShowProgressDialog(true);
getCompositeDisposable().add(getDataManager()
.getApiHelper().doDetailTV(id, URLConfig.API_KEY, getDataManager().getLanguage())
.subscribeOn(getSchedulerProvider().io())
.observeOn(getSchedulerProvider().ui())
.subscribe(detailResponse -> {
setUpData(detailResponse);
getNavigator().ShowProgressDialog(false);
}, throwable -> {
getNavigator().ShowProgressDialog(false);
}));
}
}
private void setUpData(DetailResponse detailResponse) {
if (detailResponse.getOriginal_name() != null) {
originalName.set(detailResponse.getOriginal_name());
} else if (detailResponse.getOriginal_title() != null) {
originalName.set(detailResponse.getOriginal_title());
} else {
}
if (detailResponse.getFirst_air_date() != null) {
releaseDate.set(detailResponse.getFirst_air_date());
} else {
releaseDate.set(detailResponse.getRelease_date());
}
if (!detailResponse.getOverview().equals("")) {
overview.set(detailResponse.getOverview());
} else {
overview.set(getNavigator().noDesc());
}
posterPath.set(String.valueOf(detailResponse.getPoster_path()));
voteAverage.set(String.valueOf(detailResponse.getVote_average()));
String genres = "";
for (int i = 0; i < detailResponse.getGenreList().size(); i++) {
genres = genres + detailResponse.getGenreList().get(i).getName();
if (i != detailResponse.getGenreList().size() - 1) {
genres = genres + ", ";
}
}
genreMovie.set(genres);
}
public ObservableField<String> getOriginalName() {
return originalName;
}
public ObservableField<String> getReleaseDate() {
return releaseDate;
}
public ObservableField<String> getOverview() {
return overview;
}
public ObservableField<String> getGenreMovie() {
return genreMovie;
}
public ObservableField<String> getPosterPath() {
return posterPath;
}
public ObservableField<String> getVoteAverage() {
return voteAverage;
}
}
and this is my DetailResponse Class :
public class DetailResponse {
@SerializedName("original_name")
private String original_name ;
@SerializedName("original_title")
private String original_title ;
@SerializedName("release_date")
private String release_date ;
@SerializedName("first_air_date")
private String first_air_date ;
@SerializedName("vote_average")
private Double vote_average ;
@SerializedName("overview")
private String overview ;
@SerializedName("poster_path")
private String poster_path;
@SerializedName("genres")
private List<Genre> genreList;
public String getOriginal_name() {
return original_name;
}
public String getOriginal_title() {
return original_title;
}
public String getRelease_date() {
return release_date;
}
public String getFirst_air_date() {
return first_air_date;
}
public Double getVote_average() {
return vote_average;
}
public String getOverview() {
return overview;
}
public String getPoster_path() {
return poster_path;
}
public List<Genre> getGenreList() {
return genreList;
}
public static class Genre{
@SerializedName("name")
private String name ;
public String getName() {
return name;
}
}
}
and last one, here how I try to get data in my UI using databinding, this is my layout :
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".ui.detail.DetailActivity">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="id.dicoding.eriza.moviecatalogue.ui.detail.DetailViewModel" />
</data>
<androidx.core.widget.NestedScrollView
android:id="@+id/nestedScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:animateLayoutChanges="true">
<com.github.florent37.shapeofview.shapes.ArcView
android:id="@+id/shape_header"
android:layout_width="match_parent"
android:layout_height="@dimen/size300dp"
android:alpha="0.7"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shape_arc_cropDirection="outside"
app:shape_arc_height="@dimen/size30dp"
app:shape_arc_position="bottom">
<com.flaviofaria.kenburnsview.KenBurnsView
android:id="@+id/image_header"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/poster_avengerinfinity"
app:imageDetailUrl="@{viewModel.posterPath}"
android:tint="#6F000000" />
</com.github.florent37.shapeofview.shapes.ArcView>
<com.github.florent37.shapeofview.shapes.RoundRectView
android:id="@+id/shape_poster"
android:layout_width="@dimen/size150dp"
android:layout_height="@dimen/size200dp"
android:layout_marginTop="@dimen/margin250dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/shape_header"
app:shape_roundRect_bottomLeftRadius="@dimen/corner10dp"
app:shape_roundRect_bottomRightRadius="@dimen/corner10dp"
app:shape_roundRect_topLeftRadius="@dimen/corner10dp"
app:shape_roundRect_topRightRadius="@dimen/corner10dp">
<ImageView
android:id="@+id/image_poster"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/hint_poster"
android:scaleType="fitXY"
app:imageDetailUrl="@{viewModel.posterPath}"
android:src="@drawable/poster_avengerinfinity" />
</com.github.florent37.shapeofview.shapes.RoundRectView>
<TextView
android:id="@+id/text_title"
style="@style/FontText.Title.Detail"
android:layout_marginTop="@dimen/margin15dp"
android:textSize="@dimen/font_large_size"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/shape_poster"
android:text="@{viewModel.originalName}"
tools:text="@string/hint_title" />
<TextView
android:id="@+id/text_title_release"
style="@style/FontText"
android:layout_marginTop="@dimen/margin10dp"
android:text="@string/text_release"
app:layout_constraintEnd_toStartOf="@+id/guideline"
android:visibility="@{viewModel.isConnected ? View.VISIBLE : View.GONE}"
app:layout_constraintTop_toBottomOf="@+id/text_title" />
<TextView
android:id="@+id/text_release"
style="@style/FontText"
android:layout_marginStart="@dimen/margin2dp"
android:layout_marginTop="@dimen/margin10dp"
android:text="@{viewModel.releaseDate}"
android:visibility="@{viewModel.isConnected ? View.VISIBLE : View.GONE}"
app:layout_constraintStart_toEndOf="@+id/text_title_release"
app:layout_constraintTop_toBottomOf="@+id/text_title"
tools:text="@string/hint_release" />
<TextView
android:id="@+id/text_genres"
style="@style/FontText.Normal"
android:layout_marginStart="@dimen/margin15dp"
android:layout_marginTop="@dimen/margin10dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_release"
android:text="@{viewModel.genreMovie}"
tools:text="@string/hint_genres" />
<RatingBar
android:id="@+id/rating_bar"
android:layout_width="wrap_content"
android:layout_height="45dp"
android:layout_marginStart="@dimen/margin15dp"
android:layout_marginTop="@dimen/margin5dp"
android:isIndicator="true"
android:numStars="5"
android:rating="3.8"
android:stepSize="0.1"
android:visibility="@{viewModel.isConnected ? View.VISIBLE : View.GONE}"
app:layout_constraintStart_toStartOf="parent"
app:ratingBar="@{viewModel.voteAverage}"
app:layout_constraintTop_toBottomOf="@+id/text_genres" />
<TextView
android:id="@+id/text_rating"
style="@style/FontText.Rating.Orange"
android:layout_marginStart="@dimen/margin5dp"
app:layout_constraintRight_toLeftOf="parent"
app:layout_constraintStart_toEndOf="@+id/rating_bar"
app:layout_constraintTop_toBottomOf="@+id/text_genres"
android:text="@{viewModel.voteAverage}"
tools:text="@string/hit_rating" />
<TextView
android:id="@+id/text_default_rating"
style="@style/FontText.Rating"
android:textStyle="normal"
android:visibility="@{viewModel.isConnected ? View.VISIBLE : View.GONE}"
app:layout_constraintRight_toLeftOf="parent"
app:layout_constraintStart_toEndOf="@+id/text_rating"
app:layout_constraintTop_toBottomOf="@+id/text_genres"
android:text="@string/hint_default_rating" />
<TextView
android:id="@+id/text_desc"
style="@style/FontText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin15dp"
android:layout_marginTop="@dimen/margin25dp"
android:layout_marginEnd="@dimen/margin15dp"
android:paddingBottom="@dimen/padding100dp"
app:layout_constraintTop_toBottomOf="@+id/rating_bar"
android:text="@{viewModel.overview}"
tools:text="@string/hint_desc" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</layout>
I think ViewModelshould manages UI that related data in Activity lifecycle, and tt allows to survive configuration changes in the Application, but in my case, the data is survive, but when I check on my logcat the fetchDetail() in DetailViewModel class is always call when screen rotation, and my ImageView is also refresh when I rotate my screen, so I think the viewModel not manage my UI when screen rotation. is there any problem with my code?
hope anybody can help me to show me where is my fault and what I must do. Thank you very much.
来源:https://stackoverflow.com/questions/57858035/android-ui-and-data-not-survive-when-screen-rotation-in-mvvm-pattern