Home > Blockchain >  Efficient way to fetch FireStore data into two fragments with a shared ViewModel
Efficient way to fetch FireStore data into two fragments with a shared ViewModel

Time:04-25

Context: I have a User class that looks like this:

public class User {
    private String firstname;
    private String lastname;
    private GeoPoint location;
    
    /* Constructor and getters */
    ...
}

and two Fragments in which I'll display a list of the users' names in the first fragment using a RecyclerView and a GoogleMap that displays markers of the device location of the users in the second fragment. I'm using a shared ViewModel between these two fragments to supply the data needed. The ViewModel looks like this:

@HiltViewModel
public class UsersViewModel extends ViewModel {

    private List<User> users;
    private UserRepository repository;

    @Inject
    public UsersViewModel(UserRepository repository) {
        this.repository = repository;
    }

    public void fetchUsers(OnFetchDataCallback callback) {
        repository.getUsers(callback); 
    }

    public void setUsers(List<User> users) {
        this.users = users;
    }

    public List<User> getUsers() {
        return users;
    }
}

In my repository class,

public class UserRepository {

    private final FirebaseFirestore db;

    @Inject
    public UserRepository(FirebaseFirestore db) {
        this.db = db;
    }

    public List<User> getUsers(OnFetchDataCallback callback) {
        db.collection("users")
            .addSnapshotListener(new EventListener<QuerySnapshot>() {
                @Override
                public void onEvent(@Nullable QuerySnapshot val,
                                    @FirebaseFirestoreException e) {
                    List<User> users = new ArrayList<>();
    
                    for (QueryDocumentSnapshot document : val) {
                        users.add(document.toObject(User.class);
                    }
                    callback.onFetchData(users);
                }
            });
    }
}

I have a method called getUsers() that will do the actual fetching of users from a firestore database. I also created an interface callback that looks like this

public interface OnFetchDataCallback {
    void onFetchData(List<User> users);
}

so that later I can access the fetched users within my two fragments. In my first fragment,

@AndroidEntryPoint
public class DisplayUsersFragment extends Fragment {
    private FragmentDisplayUsersBinding binding;
    private UsersViewModel viewModel;
    private UsersAdapter adapter;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        viewModel = new ViewModelProvider(this).get(UsersViewModel.class);
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater,
                             @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        binding = FragmentDisplayUsersBinding.inflate(inflater, container, false);
        binding.usersRecyclerView.setLayoutManager(
            new LinearLayoutManager(getContext()));

        viewModel.fetchUsers(new OnFetchDataCallback() {
            @Override
            public void onFetchData(List<User> users) {
                viewModel.setUsers(users);
                adapter = new UsersAdapter(viewModel.getUsers());
                binding.usersRecyclerView.setAdapter(adapter);
            }
        });
        return binding.getRoot();
    }
}

I called the fetchUsers() and accessed the List of users inside the callback and set it to my viewModel's users before constructing an adapter for my RecyclerView and providing it with the data it needs by calling viewModel.getUsers() and passing it as the constructor argument for the adapter. Now, my problem here is that in my second fragment,

@AndroidEntryPoint
public class UsersMapFragment extends Fragment implements OnMapReadyCallback {

    private FragmentUsersMapBinding binding;
    private UsersViewModel viewModel;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        viewModel = new ViewModelProvider(this).get(UsersViewModel.class);
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater,
                             @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        binding = FragmentUsersMapBinding.inflate(inflater, container, false);
        return binding.getRoot();
    }

    @Override
    public void onViewCreated(@NonNull View view,
                              @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        SupportMapFragment mapFragment = SupportMapFragment.newInstance();
        getChildFragmentManager().beginTransaction()
            .add(R.id.users_map_fragment_container, mapFragment)
            .commit()
        mapFragment.getMapAsync(this);
    }

    @Override
    public void onMapReady(@NonNull GoogleMap googleMap) {
         viewModel.fetchUsers(new OnFetchDataCallback() {
            @Override
            public void onFetchData(List<User> users) {
                viewModel.setUsers(users);
                for (User user : viewModel.getUsers()) {
                    googleMap.addMarker(new MarkerOptions()
                                .position(new LatLng(
                                    user.getLocation().getLatitude(),
                                    user.getLocation().getLongitude())
                                )
                                .title(user.getFirstname()   " " 
                                       user.getLastname()
                                )
                            );
                }
            }
        });
    }
}

I called again fetchUsers() inside onMapReady() so that I can access inside its callback the users and use them to create markers in the map. Essentially, I tried to call fetchUsers() twice, but I think that there is a more efficient way to do this, but I have no idea how to handle this. Please help me, sorry my English is not good.

CodePudding user response:

You can use live data in your ViewModel: See the code given below:

@HiltViewModel
public class UsersViewModel extends ViewModel {

    private List<User> users;
    MutableLiveData<List<User>> usersLiveData = new MutableLiveData();
    private UserRepository repository;

    @Inject
    public UsersViewModel(UserRepository repository) {
        this.repository = repository;
    }

    public void fetchUsers() {
        repository.getUsers(new OnFetchDataCallback() {
            @Override
            public void onFetchData(List<User> users) {
            usersLiveData.setValue(users);
            // you can observe this data after calling fetch users from your activity
            setUsers(users);
}); 
    }

    public void setUsers(List<User> users) {
        this.users = users;
    }

    public List<User> getUsers() {
        return users;
    }
}

In the activity:

mViewModel.fetchUsers();

And in both fragments:

viewModel.usersLiveData.observe(this,{ list->
   // user your list here in both fragments
   // rather than fetch users on both fragments
});
  • Related