Eric
Eric

Reputation: 37

How Best To Implement ViewModel( in AndroidX) So Data Survives Configuration Changes

I am trying to implement a ViewModel architecture for a RecyclerView in AndroidX, following the example as stated in enter link description here and enter link description here. Items in the recyclerView get selected on position clicked, but for some reason, the selected item de-select and revert to default after the device is rotated and configuration changed. I know there have been answers for questions like this in the past, but all I have seen are either not directly applicable in my case or are simply for deprecated cases.

CAN SOMEONE PLEASE TELL ME WHAT I AM DOING WRONG!

Below are snippets from my Code:

Dependencies added

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'

}

Repository Class: public class 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);
        }
    }
}

}

My ViewModel 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 Implementing 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");
}

}

Upvotes: 0

Views: 289

Answers (1)

Kozmotronik
Kozmotronik

Reputation: 2518

For saving and restoring UI related data you better use savedInstanceState Bundle to survive the last state. To achive this you simply override two methods in you UI activity. See the sample code snippet below.

In your 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;
}

In your view model 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);
    }
}

In you UI activity which uses the 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;
    }
}

There you go. Try this logic carefully without bugs. Then I think you will achive what you want to get.

Upvotes: 1

Related Questions