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 Fragment
s 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
});