Android : UI and data not survive when screen rotation in MVVM pattern

本秂侑毒 提交于 2019-12-11 18:04:05

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!