如何最好地实现 ViewModel(在 AndroidX 中)以便数据在配置更改后仍然存在
How Best To Implement ViewModel( in AndroidX) So Data Survives Configuration Changes
我正在尝试按照 enter link description here and enter link description here 中所述的示例,在 AndroidX 中为 RecyclerView 实现 ViewModel 架构。 recyclerView 中的项目在单击的位置得到 selected,但由于某种原因,selected 项目在设备旋转和配置更改后取消 select 并恢复为默认值。我知道过去有类似问题的答案,但我所看到的要么不直接适用于我的案例,要么仅适用于已弃用的案例。
谁能告诉我我做错了什么!
以下是我的代码片段:
已添加依赖项
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'
}
存储库Class:
public class 主题库 {
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);
}
}
}
}
我的视图模型class
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");
}
}
为了保存和恢复 UI 相关数据,您最好使用 savedInstanceState Bundle 来保存最后一个状态。要实现这一点,您只需重写 UI activity 中的两个方法。请参阅下面的示例代码片段。
在您的 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;
}
在你的视图模型中class
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);
}
}
在你UIactivity里面使用了RecyclerView
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;
}
}
给你。仔细尝试这个逻辑没有错误。那么我想你会得到你想要的。
我正在尝试按照 enter link description here and enter link description here 中所述的示例,在 AndroidX 中为 RecyclerView 实现 ViewModel 架构。 recyclerView 中的项目在单击的位置得到 selected,但由于某种原因,selected 项目在设备旋转和配置更改后取消 select 并恢复为默认值。我知道过去有类似问题的答案,但我所看到的要么不直接适用于我的案例,要么仅适用于已弃用的案例。
谁能告诉我我做错了什么!
以下是我的代码片段:
已添加依赖项
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'
}
存储库Class: public class 主题库 {
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);
}
}
}
}
我的视图模型class
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");
}
}
为了保存和恢复 UI 相关数据,您最好使用 savedInstanceState Bundle 来保存最后一个状态。要实现这一点,您只需重写 UI activity 中的两个方法。请参阅下面的示例代码片段。
在您的 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;
}
在你的视图模型中class
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);
}
}
在你UIactivity里面使用了RecyclerView
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;
}
}
给你。仔细尝试这个逻辑没有错误。那么我想你会得到你想要的。