【问题标题】:How Best To Implement ViewModel( in AndroidX) So Data Survives Configuration Changes如何最好地实现 ViewModel(在 AndroidX 中)以使数据在配置更改中幸存下来
【发布时间】:2020-12-25 10:09:37
【问题描述】:

我正在尝试按照enter link description hereenter link description here 中所述的示例在AndroidX 中为RecyclerView 实现ViewModel 架构。 recyclerView 中的项目在单击位置时被选中,但由于某种原因,在设备旋转和配置更改后,所选项目取消选择并恢复为默认值。我知道过去曾有此类问题的答案,但我所看到的要么不直接适用于我的案例,要么仅适用于已弃用的案例。

谁能告诉我我做错了什么!

以下是我的代码中的 sn-ps:

添加的依赖项

dependencies {

def lifecycle_version = "2.2.0"

// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"

// LiveData
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"

// Saved state module for ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"

// Annotation processor
annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"

implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha02'

}

存储库类: 公共类 TopicRepository {

private Application application;
private SharedPreferences sharedPreferences;
private ArrayList<RootTopic> topicGroupList;
private MutableLiveData<ArrayList<RootTopic>>topicGroupMLD;

public TopicRepository(Application application) {
    this.application = application;
    
}

public LiveData<ArrayList<RootTopic>> getRootTopicLD(String subject){
    if (topicGroupMLD == null){
        topicGroupMLD = new MutableLiveData<ArrayList<RootTopic>>();
        generateTopicGroup(subject);
    }
        return topicGroupMLD;
}

private void generateTopicGroup(final String subject){
    Log.d(TAG, "generateTopicGroup: CALLED");
    isRequestingMLD.postValue(true);
    final String subjectTopicGroupList = subject + "TopicGroupList";

    sharedPreferences = application.getSharedPreferences(AppConstant.Constants.PACKAGE_NAME, Context.MODE_PRIVATE);
    String serializedTopicGroup = sharedPreferences.getString(subjectTopicGroupList, null);
     if (serializedTopicGroup != null){

         Gson gson = new Gson();
         Type type = new TypeToken<ArrayList<RootTopic>>(){}.getType();
         topicGroupList = gson.fromJson(serializedTopicGroup, type);
         topicGroupMLD.postValue(topicGroupList);

     }else {//       - Not saved in SP

         Log.d(TAG, "getTopicGroup: NOT IN SP");
         new ActiveConnectionCheck(new ActiveConnectionCheck.Consumer() {
             @Override
             public void accept(Boolean internet) {
                 Log.d(TAG, "accept: CHECKED INTERNET");
                 if (internet){
                     Log.d(TAG, "accept: INTERNET CONNECTION = TRUE");
                     internetCheckMLD.postValue(AppConstant.Constants.IS_INTERNET_REQUEST_SUCCESS);

                     FirebaseFirestore fbFStore = FirebaseFirestore.getInstance();
                     CollectionReference lectureRef = fbFStore.collection(subject);
                     lectureRef.orderBy(AppConstant.Constants.POSITION, Query.Direction.ASCENDING)
                             .get().addOnSuccessListener(
                             new OnSuccessListener<QuerySnapshot>() {
                                 @Override
                                 public void onSuccess(QuerySnapshot queryDocumentSnapshots) {

                                     ArrayList<Topic>topicList = new ArrayList<>();
                                     ArrayList<String> rootTitleList = new ArrayList<>();

                                     for (QueryDocumentSnapshot snapshot : queryDocumentSnapshots){
                                         Topic topic = snapshot.toObject(Topic.class);
                                         topicList.add(topic);
                                     }

                                     Log.d(TAG, "onSuccess: TopicListSize = " + topicList.size());

                                     for (Topic topic : topicList){
                                         String rootTopicString = topic.getRootTopic();
                                         if (!rootTitleList.contains(rootTopicString)){
                                             rootTitleList.add(rootTopicString);
                                         }
                                     }

                                     Log.d(TAG, "onSuccess: RootTitleListSize = " + rootTitleList.size());

                                    for (int x = 0; x < rootTitleList.size(); x ++){
                                        RootTopic rootTopic = new RootTopic(rootTitleList.get(x), new ArrayList<Topic>());
                                        topicGroupList = new ArrayList<>();
                                        topicGroupList.add(rootTopic);
                                    }

                                     for (int e = 0; e < topicList.size(); e++){
                                         addTopicToGroup(topicGroupList, topicList.get(e));
                                     }

                                     topicGroupMLD.postValue(topicGroupList);
                                     Gson gson = new Gson();
                                     String serializedTopicGroup = gson.toJson(topicGroupList);
                                     sharedPreferences.edit().putString(subjectTopicGroupList, serializedTopicGroup).apply();

                                     Log.d(TAG, "onSuccess: TOPICGROUPSIZE = " + topicGroupList.size());
                                     Log.d(TAG, "onSuccess: SERIALIZED GROUP = " + serializedTopicGroup);
                                     isRequestingMLD.postValue(false);

                                 }
                             }
                     ).addOnFailureListener(
                             new OnFailureListener() {
                                 @Override
                                 public void onFailure(@NonNull Exception e) {
                                     isRequestingMLD.postValue(false);
                                     Log.d(TAG, "onFailure: FAILED TO GET TOPICLIST e = " + e.toString());
                                 }
                             }
                     );

                 }else {
                     internetCheckMLD.postValue(AppConstant.Constants.IS_INTERNET_REQUEST_FAIL);
                     Log.d(TAG, "accept: InternetCONECTION = " + false);
                 }
             }
         });

     }

}

private void addTopicToGroup(ArrayList<RootTopic>rootGroup, Topic topic){
    for (int x = 0; x < rootGroup.size(); x++){
        RootTopic rootTopic = rootGroup.get(x);
        if (rootTopic.getRootTopicName().equals(topic.getRootTopic())){
            rootTopic.getTopicGroup().add(topic);
        }
    }
}

}

我的 ViewModel 类

public class LectureViewModel extends AndroidViewModel {

public static final String TAG = AppConstant.Constants.GEN_TAG + ":LectureVM";

private Application application;
private TopicRepository topicRepository;
private ArrayList<RootTopic> topicGroupList;


public LectureViewModel(@NonNull Application application) {
    super(application);
    this.application = application;
    topicRepository = new TopicRepository(application);
   
}


public LiveData<ArrayList<RootTopic>> getRootTopicListLD(String subject){

    return topicRepository.getRootTopicLD(subject);
}

}

Activity 实现 ViewModel

public class LectureRoomActivity extends AppCompatActivity {

public static final String TAG = AppConstant.Constants.GEN_TAG + " LecRoom";
private LectureViewModel lectureRoomVM;
private String subject;
private RecyclerView mainRecyclerView;

private RootTopicAdapter rootTopicAdapter;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_lecture_room);

    Intent intent = getIntent();
    subject = intent.getStringExtra(AppConstant.Constants.SUBJECT);

    mainRecyclerView = findViewById(R.id.recyclerView);
    
    downloadVM = new ViewModelProvider(this).get(DownloadLectureViewModel.class);
    lectureRoomVM = new ViewModelProvider(this).get(LectureViewModel.class);
    lectureRoomVM.getRootTopicListLD(subject).observe(
            this,
            new Observer<ArrayList<RootTopic>>() {
        @Override
        public void onChanged(ArrayList<RootTopic> rootTopics) {
            if (rootTopics != null){
                currentTopic = lectureRoomVM.getCursorTopic(subject, rootTopics);
                setUpRec(rootTopics, currentTopic);
            }
        }
    });

}


private void setUpRec( ArrayList<RootTopic>topicGroup, CursorTopic currentTopic){
     rootTopicAdapter = new RootTopicAdapter(topicGroup,
            new ArrayList<String>(), currentTopic.getParentPosition(),
            currentTopic.getCursorPosition());

    LinearLayoutManager linearLayoutManager = new LinearLayoutManager(
            this, RecyclerView.VERTICAL,false);

    mainRecyclerView.setHasFixedSize(true);
    mainRecyclerView.setLayoutManager(linearLayoutManager);
    mainRecyclerView.setAdapter(rootTopicAdapter);

    Log.d(TAG, "setUpRec: SETTING REC");
}

}

【问题讨论】:

  • 请说明您希望它保留哪些数据,并且在配置更改后它不会保留。您希望保留哪些数据?
  • @Kozmotronik,自定义主题列表。关键是,我的 Recyclerview 中的选定项目在配置更改后重置为默认位置。我希望实现的是设备旋转后选择的位置保持不变。
  • 对于与视图相关的状态,例如 RecyclerView 的最后选择索引,如果您想生存,请使用 ViewModel 类中的变量。在那里,我将向您展示一个示例代码...

标签: java android mvvm androidx android-viewmodel


【解决方案1】:

为了保存和恢复 UI 相关数据,您最好使用 savedInstanceState Bundle 来保留最后一个状态。要实现这一点,您只需覆盖 UI 活动中的两个方法。请参阅下面的示例代码 sn-p。

在您的 RootTopicAdapter 中

// Add this where you detect the item click, probably in your adaptor class
private int lastRecyclerViewIndex; // define the variable to hold the last index
...

@Override
public void onClick(View v) {
    lastRecyclerViewIndex = getLayoutPosition();
}

public int getLastIndex() {
    return lastRecyclerViewIndex;
}

在您的视图模型类中

public class LectureViewModel extends AndroidViewModel {

    public static final String TAG = AppConstant.Constants.GEN_TAG + ":LectureVM";

    private Application application;
    private TopicRepository topicRepository;
    private ArrayList<RootTopic> topicGroupList;
    public boolean mustRestore; // Is there any data to restore
    public int lasIndexSelected;


    public LectureViewModel(@NonNull Application application) {
        super(application);
        this.application = application;
        topicRepository = new TopicRepository(application);
    
    }


    public LiveData<ArrayList<RootTopic>> getRootTopicListLD(String subject){

        return topicRepository.getRootTopicLD(subject);
    }
}

在你使用 RecyclerView 的 UI 活动中

public class LectureRoomActivity extends AppCompatActivity {
    ...
    private LectureViewModel lectureRoomVM;
    ...
    private RecyclerView mainRecyclerView;

    private RootTopicAdapter rootTopicAdapter;
    
    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_lecture_room);

        Intent intent = getIntent();
        subject = intent.getStringExtra(AppConstant.Constants.SUBJECT);

        mainRecyclerView = findViewById(R.id.recyclerView);
        
        downloadVM = new ViewModelProvider(this).get(DownloadLectureViewModel.class);
        lectureRoomVM = new ViewModelProvider(this).get(LectureViewModel.class);
        lectureRoomVM.getRootTopicListLD(subject).observe(
                this,
                new Observer<ArrayList<RootTopic>>() {
            @Override
            public void onChanged(ArrayList<RootTopic> rootTopics) {
                if (rootTopics != null){
                    currentTopic = lectureRoomVM.getCursorTopic(subject, rootTopics);
                    setUpRec(rootTopics, currentTopic);
                    // Exactly here, after setting up the data get your index for example
                    if(lectureRoomVM.mustRestore){
                        // Check the item count in the adaptor to avoid crashes
                        if(mainRecyclerView.getAdapter().getItemCount >= lastRecyclerViewIndex){
                            mainRecyclerView.findViewHolderForAdapterPosition(lastRecyclerViewIndex).itemView.performClick();
                        }
                        // After the restoration set the mustRestore to false
                        lectureRoomVM.mustRestore = false;
                    }
                }
            }
        });
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(E, "onDestroy");
        /* 
         * Here just set the mustRestore to true in order to be able to restore in onCreate method.
         * If the application itself is not destroyed your data will still be live in the
         * memory thanks to the ViewModel's life cycle awarness.
         */
        lectureRoomVM.mustRestore = true;
    }
}

给你。仔细尝试这个逻辑,没有错误。那我想你会得到你想要的。

【讨论】:

  • 非常感谢您的努力,但对我来说真正的问题是让 ViewModel 正常工作。事实是,我基本上想绕过 SaveInstanceState 和 onRestoreInstanceState 回调的整个想法,不仅仅是为了这个 recyclerview,也是为了项目的其他部分。
  • 我明白了,那么你要做的比我的代码更容易。只需在视图模型实例中创建一个变量,比如 lastIndex,然后执行我在 InstanceState 覆盖方法中所做的操作。您甚至不必保存它,因为 ViewModel 对象在整个应用程序被销毁之前不会被销毁。在 onCreate 方法中,在设置 recyclerview 后读取 lastIndex 值,就像我在 onRestoreInstanceState 方法中所做的那样,您将获得在活动销毁之前留下的内容。然后,您可以对想要保留的其他数据使用相同的逻辑。是清楚还是要我为它写一个基本的sn-p?
  • 好的,我试试看。我很快就会恢复
  • 仍然无法正常工作,ViewModel 让我失望了。尝试在 VieModel 类中创建一个 int 变量,我可以使用 Activity 中的调用在项目单击时更新它的值。然后,我做了一个 Livedata 回调来获取位置的当前值。那就是我将数据传递给我的适配器的地方。在我旋转设备之前它工作正常。配置更改后,LiveData 立即将其重置为默认值,而不保留它的最后一个值。我认为依赖或 ViewModel 设置有问题
  • 如果我弄错了,请帮助检查我添加的依赖项和我的 ViewModel 初始化。 ViewModel 概念看起来很基本,但我不明白为什么 LiveData 回调会在配置更改时不断重置
猜你喜欢
  • 2023-04-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-16
  • 1970-01-01
  • 2012-03-29
  • 2018-04-12
  • 1970-01-01
相关资源
最近更新 更多