Hi I am new to android development and am trying to get my head around the architecture but have spent the past 2 days trying to figure out LiveData
I am using an SDK which allows me to scan some sensors, I display the sensors on the device and then toggle a switch to connect the sensor. Once the sensor is connected I have created a button which runs a funcion called startMeasuring() everytime some data is measured a callback is hit and this is where my struggle begins.
In my MainActivity I have the following code which is ran once I toggle the switch to connect to the sensor.
public void onConnectedSensorClick(BluetoothDevice sensor, Integer position, Boolean checked) {
XsensDotDevice xsDevice = new XsensDotDevice(this, sensor, new XsDevice(this));
if (checked) {
xsDevice.connect();
mMainActivityViewModel.addConnectedSensor(xsDevice);
}
}
The XsensDotDevice() expects 3 parameters the context, scanned sensor and the callback class.
In my callback calss the following callback function is overridden
#Override
public void onXsensDotDataChanged(String s, XsensDotData xsensDotData) {
}
This function is the one which gets triggered when I start measuring and the sensor sends the device a measurement.
I am have created a ViewModel and Repository which is what I want to use to store this data so I can access it back in my MainActivity using an Observer
I got the ViewModel and Repository working for my scanned devices but I'm not sure how to get this working for the measurement data because I can't access the ViewModel in my callback class XsDevice() to pass the data to the Repository
What I want to do is somehow pass the XsensDotData (measurement data) to the SensorDataRepository and then create an Observer in my MainActivity like so.
mMainActivityViewModel.getSensorData().observe(this, new Observer<XsensDotData>() {
#Override
public void onChanged(XsensDotData xsensDotData) {
for(int i = 0; i< xsensDotData.getFreeAcc().length; i++){
Log.d("Sensor Data Acceleration " + i, String.valueOf(xsensDotData.getFreeAcc()[i]));
}
}
});
I have already created a Repository and ViewModel which i will show below
Repository
public class SensorDataRepository {
private static SensorDataRepository instance;
private XsensDotData dataSet = new XsensDotData();
public static SensorDataRepository getInstance() {
if (instance == null) {
instance = new SensorDataRepository();
}
return instance;
}
public MutableLiveData<XsensDotData> getSensorData() {
MutableLiveData<XsensDotData> data = new MutableLiveData<>();
data.setValue(dataSet);
return data;
}
public void addSensorData(XsensDotData data) {
dataSet = data;
}
}
ViewModel
public class MainActivityViewModel extends ViewModel {
private MutableLiveData<ArrayList<BluetoothDevice>> mScannedSensors;
private ScannedSensorRepository mScannedSensorRepo;
private MutableLiveData<ArrayList<XsensDotDevice>> mConnectedSensors;
private ConnectedSensorRepository mConnectedSensorRepo;
private MutableLiveData<XsensDotData> mSensorData;
private SensorDataRepository mSensorDataRepo;
public void init() {
if (mScannedSensors != null) {
return;
}
mScannedSensorRepo = ScannedSensorRepository.getInstance();
mScannedSensors = mScannedSensorRepo.getScannedSensors();
if (mConnectedSensorRepo != null) {
return;
}
mConnectedSensorRepo = ConnectedSensorRepository.getInstance();
mConnectedSensors = mConnectedSensorRepo.getConnectedSensors();
if (mSensorDataRepo != null) {
return;
}
mSensorDataRepo = SensorDataRepository.getInstance();
mSensorData = mSensorDataRepo.getSensorData();
}
public LiveData<ArrayList<BluetoothDevice>> getScannedSensors() {
return mScannedSensors;
}
public void addScannedSensor(BluetoothDevice device) {
mScannedSensorRepo.addScannedSensors(device);
}
public LiveData<ArrayList<XsensDotDevice>> getConnectedSensors() {
return mConnectedSensors;
}
public void addConnectedSensor(XsensDotDevice device) {
mConnectedSensorRepo.addConnectedSensors(device);
}
public LiveData<XsensDotData> getSensorData() {
return mSensorData;
}
public void addSensorData(XsensDotData data) {
mSensorDataRepo.addSensorData(data);
}
}
I included the code for the scanned and connect devices in the ViewModel in case it come in handy and helps explain whats going on.
Thank you for any help!
Here is a simple example of how I use LiveData. In my view model I will have a value as so :
var isInternetAvailable = MutableLiveData<Boolean>().apply { value = true }
in my activity I will have the code:
viewmodel.isInternetAvailable.observe(this, Observer {
// execute your logic here
var theValue = viewmodel.isInternetAvailable.value!!
}
Then in my viewModel when the internet has changed I will use
viewmodel.isInternetAvailable.postValue(true)
So for your code - as far as I can see, you're observing the function but not posting to it in order to trigger your observer function
You can use
MutableLiveData<XsensDotData> data = new MutableLiveData<>();
and then
data.postValue(dataSet)
That should hopefully trigger your observer
Related
I'm not sure where I'm implementing MediatorLiveData incorrectly.
I have a Repository class that exposes a mutable live data object:
private MutableLiveData<List<UserTransaction>> mTransactionsLiveData
= new MutableLiveData<>();
public MutableLiveData<List<UserTransaction>> getTransactionsLiveData() {
return mTransactionsLiveData;
}
I have passed reference of this MutableLiveData to my ViewModel class with a getter:
public class UserTransactionsViewModel extends AndroidViewModel {
private LiveData<List<UserTransaction>> mTransactionsLiveData;
private SharedRepo mRepo;
public UserTransactionsViewModel(#NonNull Application application) {
super(application);
mRepo = new SharedRepo(application);
mTransactionsLiveData = mRepo.getTransactionsLiveData();
}
public LiveData<List<UserTransaction>> getTransactionsLiveData() {
return mTransactionsLiveData;
}
public void getUserTransactions(int userId) {
mRepo.getUserTransactions(userId);
}
}
And in my Fragment class, I am observing it:
mViewModel.getTransactionsLiveData().observe(getViewLifecycleOwner(), new Observer<List<UserTransaction>>() {
#Override
public void onChanged(List<UserTransaction> list) {
//Observing it here
loadDataIntoRecycler(list);
}
});
Where I am confused:
I am now trying to add a MediatorLiveData to format the Data that the ViewModel receives from the Repository class.
So I first added a new new MediatorLiveData<>() in the ViewModel
private MediatorLiveData<List<UserTransaction>> mediatorLiveData
= new MediatorLiveData<>();
and attached the .addSource
private MediatorLiveData<List<UserTransaction>> mediatorLiveData = new MediatorLiveData<>();
public UserTransactionsViewModel(#NonNull Application application) {
super(application);
mRepo = new SharedRepo(application);
mTransactionsLiveData = mRepo.getTransactionsLiveData();
mediatorLiveData.addSource(mTransactionsLiveData, new Observer<List<UserTransaction>>() {
#Override
public void onChanged(List<UserTransaction> userTransactions) {
Log.d(TAG, "onChanged: Formatting data...");
for(int i = 0; i < list.size(); i++){
list.get(i).setTime("Changing time");
}
}
});
}
I am confused, do I make the observer in the Fragment listen to the MediatorLiveData, instead of the LiveData? Because the .onChanged method on mediatorLiveData is never called.
The .onChanged method on the mediatorLiveData is never called because it's not observed.
And, if you have already tried to observe the mediatorLiveData from the Fragment and .onChanged method is still not called, it means the observee is never changed. Never changed means never being setValue or postValue.
Like this:
mediatorLiveData.addSource(mTransactionsLiveData,
new Observer<List<UserTransaction>>() {
#Override
public void onChanged(List<UserTransaction> userTransactions) {
Log.d(TAG, "onChanged: Formatting data...");
for(int i = 0; i < list.size(); i++){
list.get(i).setTime("Changing time");
}
mediatorLiveData.setValue(list);
}
});
Or, using Transformations.map (and Java 8 lambda) may be cleaner (while observe userTransactions from the Fragment):
public LiveData<List<UserTransaction>> userTransactions;
userTransactions = Transformations.map(mTransactionsLiveData, list -> {
Log.d(TAG, "onChanged: Formatting data...");
for(int i = 0; i < list.size(); i++){
list.get(i).setTime("Changing time");
}
return list;
});
MediatorLiveData only observes its sources while it's active (has an active observer) so yes, you should observe it in your Fragment. When there are no active observers, onChanged will not be called.
The structure of my application is as follows:
MainActivity(Activity) containing Bottom Navigation View with three fragments nested below
HomeFragment(Fragment) containing TabLayout with ViewPager with following two tabs
Journal(Fragment)
Bookmarks(Fragment)
Fragment B(Fragment)
Fragment C(Fragment)
I am using Room to maintain all the records of journals. I'm observing one LiveData object each in Journal and Bookmarks fragment. These LiveData objects are returned by my JournalViewModel class.
JournalDatabase.java
public abstract class JournalDatabase extends RoomDatabase {
private static final int NUMBER_OF_THREADS = 4;
static final ExecutorService dbWriteExecutor = Executors.newFixedThreadPool(NUMBER_OF_THREADS);
private static JournalDatabase INSTANCE;
static synchronized JournalDatabase getInstance(Context context) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), JournalDatabase.class, "main_database")
.fallbackToDestructiveMigration()
.build();
}
return INSTANCE;
}
public abstract JournalDao journalDao();
}
JournalRepository.java
public class JournalRepository {
private JournalDao journalDao;
private LiveData<List<Journal>> allJournals;
private LiveData<List<Journal>> bookmarkedJournals;
public JournalRepository(Application application) {
JournalDatabase journalDatabase = JournalDatabase.getInstance(application);
journalDao = journalDatabase.journalDao();
allJournals = journalDao.getJournalsByDate();
bookmarkedJournals = journalDao.getBookmarkedJournals();
}
public void insert(Journal journal) {
JournalDatabase.dbWriteExecutor.execute(() -> {
journalDao.insert(journal);
});
}
public void update(Journal journal) {
JournalDatabase.dbWriteExecutor.execute(() -> {
journalDao.update(journal);
});
}
public void delete(Journal journal) {
JournalDatabase.dbWriteExecutor.execute(() -> {
journalDao.delete(journal);
});
}
public void deleteAll() {
JournalDatabase.dbWriteExecutor.execute(() -> {
journalDao.deleteAll();
});
}
public LiveData<List<Journal>> getAllJournals() {
return allJournals;
}
public LiveData<List<Journal>> getBookmarkedJournals() {
return bookmarkedJournals;
}
}
JournalViewModel.java
public class JournalViewModel extends AndroidViewModel {
private JournalRepository repository;
private LiveData<List<Journal>> journals;
private LiveData<List<Journal>> bookmarkedJournals;
public JournalViewModel(#NonNull Application application) {
super(application);
repository = new JournalRepository(application);
journals = repository.getAllJournals();
bookmarkedJournals = repository.getBookmarkedJournals();
}
public void insert(Journal journal) {
repository.insert(journal);
}
public void update(Journal journal) {
repository.update(journal);
}
public void delete(Journal journal) {
repository.delete(journal);
}
public void deleteAll() {
repository.deleteAll();
}
public LiveData<List<Journal>> getAllJournals() {
return journals;
}
public LiveData<List<Journal>> getBookmarkedJournals() {
return bookmarkedJournals;
}
}
I'm instantiating this ViewModel inside onActivityCreated() method of both Fragments.
JournalFragment.java
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
JournalFactory factory = new JournalFactory(requireActivity().getApplication());
journalViewModel = new ViewModelProvider(requireActivity(), factory).get(JournalViewModel.class);
journalViewModel.getAllJournals().observe(getViewLifecycleOwner(), new Observer<List<Journal>>() {
#Override
public void onChanged(List<Journal> list) {
journalAdapter.submitList(list);
}
});
}
BookmarksFragment.java
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
JournalFactory factory = new JournalFactory(requireActivity().getApplication());
journalViewModel = new ViewModelProvider(requireActivity(), factory).get(JournalViewModel.class);
journalViewModel.getBookmarkedJournals().observe(getViewLifecycleOwner(), new Observer<List<Journal>>() {
#Override
public void onChanged(List<Journal> list) {
adapter.submitList(list);
}
});
}
However, the problem when I use this approach is as I delete make some changes in any of the Fragment like delete or update some Journal some other Journal's date field changes randomly.
I was able to solve this issue by using single LiveData object and observe it in both fragments. The changes I had to make in BookmarkFragment is as follows:
BookmarksFragment.java
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
JournalFactory factory = new JournalFactory(requireActivity().getApplication());
journalViewModel = new ViewModelProvider(requireActivity(), factory).get(JournalViewModel.class);
journalViewModel.getAllJournals().observe(getViewLifecycleOwner(), new Observer<List<Journal>>() {
#Override
public void onChanged(List<Journal> list) {
List<Journal> bookmarkedJournals = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
if (list.get(i).getBookmark() == 1)
bookmarkedJournals.add(list.get(i));
}
adapter.submitList(bookmarkedJournals);
}
});
}
It works properly now.
However, I want to know why it didn't work using my first approach which was to use two different LiveData objects and observe them in different fragments.
Are multiple LiveData objects not meant to be used in single ViewModel?
OR
Are two instances of same ViewModel not allowed to exist together while making changes and fetching different LiveData objects from the same table simultaneously?
I found out the reason causing this problem.
As I was using LiveData with getViewLifecycleOwner() as the LifecycleOwner, the observer I passed as parameter was never getting removed. So, after switching to a different tab, there were two active observers observing different LiveData objects of same ViewModel.
The way this issue can be solved is by storing the LiveData object in a variable then removing the observer as you switch to different fragment.
In my scenario, I solved this issue by doing the following:
//store LiveData object in a variable
LiveData<List<Journal>> currentLiveData = journalViewModel.getAllJournals();
//observe this livedata object
currentLiveData.observer(observer);
Then remove this observer in a suitable Lifecycle method or anywhere that suits your needs like
#Override
public void onDestroyView() {
super.onDestroyView();
//if you want to remove all observers
currentLiveData.removeObservers(getViewLifecycleOwner());
//if you want to remove particular observers
currentLiveData.removeObserver(observer);
}
Even though I am using ViewModel, whenever the device is rotated, the data in the Recyclerview disappears. I had to put the makeSearch() method inside the onClick() method because I need to get the text that the button grabs and use it as the search parameter. Is there a better way I can handle this to avoid this problem? My code is right here:
SearchActivity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search);
// What happens when the search button is clicked
materialButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (Objects.requireNonNull(textInputEditText.getText()).toString().isEmpty()) {
textInputEditText.setError("Type a search query");
} else {
mSearchInput = Objects.requireNonNull(textInputEditText.getText()).toString();
textInputEditText.setText("");
makeSearch();
}
}
});
}
// Gets the ViewModel, Observes the Question LiveData and delivers it to the Recyclerview
private void makeSearch() {
final SearchAdapter searchAdapter = new SearchAdapter();
SearchViewModel mSearchViewModel = new ViewModelProvider(this,
new CustomSearchViewModelFactory(new SearchRepository())).get(SearchViewModel.class);
mSearchViewModel.setQuery(mSearchInput);
mSearchViewModel.getQuestionLiveData().observe(this, new Observer<List<Question>>() {
#Override
public void onChanged(List<Question> questions) {
mQuestions = questions;
searchAdapter.setQuestions(questions);
}
});
mRecyclerView.setAdapter(searchAdapter);
searchAdapter.setOnClickListener(mOnClickListener);
}
SearchViewModel:
public class SearchViewModel extends ViewModel {
private SearchRepository mSearchRepository;
private MutableLiveData<String> mSearchLiveData = new MutableLiveData<>();
private LiveData<List<Question>> mQuestionLiveData = Transformations.switchMap(mSearchLiveData, (query) -> {
return mSearchRepository.getQuestions(query);
});
SearchViewModel(SearchRepository searchRepository) {
this.mSearchRepository = searchRepository;
}
public LiveData<List<Question>> getQuestionLiveData() {
return mQuestionLiveData;
}
public void setQuery(String query) {
mSearchLiveData.setValue(query);
}
}
SearchRepository:
public class SearchRepository {
//private String inTitle;
private MutableLiveData<List<Question>> mQuestions = new MutableLiveData<>();
public SearchRepository() {
//getQuestionsWithTextInTitle();
}
private void getQuestionsWithTextInTitle(String inTitle) {
ApiService apiService = RestApiClient.getApiService(ApiService.class);
Call<QuestionsResponse> call = apiService.getQuestionsWithTextInTitle(inTitle);
call.enqueue(new Callback<QuestionsResponse>() {
#Override
public void onResponse(Call<QuestionsResponse> call, Response<QuestionsResponse> response) {
QuestionsResponse questionsResponse = response.body();
if (questionsResponse != null) {
mQuestions.postValue(questionsResponse.getItems());
//shouldShowData = true;
} else {
Log.d("SearchRepository", "No matching question");
//shouldShowData = false;
}
}
#Override
public void onFailure(Call<QuestionsResponse> call, Throwable t) {
//shouldShowData = false;
t.printStackTrace();
}
});
}
public LiveData<List<Question>> getQuestions(String inTitle) {
getQuestionsWithTextInTitle(inTitle);
return mQuestions;
}
}
Your approach of passing the search input in through your CustomSearchViewModelFactory and into the constructor for the ViewModel and into the constructor for your SearchRepository isn't going to work in any case. While the first time you search your CustomSearchViewModelFactory creates the ViewModel, the second time you hit search, your SearchViewModel is already created and your factory is not invoked a second time, meaning you never get the second query.
Instead, you should file the ViewModel Overview documentation, and use Transformations.switchMap() to convert your input (the search string) into a new LiveData<List<Question>> for that given query.
This means that your ViewModel would look something like
public class SearchViewModel extends ViewModel {
private SearchRepository mSearchRepository;
private MutableLiveData<String> mSearchLiveData = new MutableLiveData<String>();
private LiveData<List<Question>> mQuestionLiveData =
Transformations.switchMap(mSearchLiveData, (query) -> {
return mSearchRepository.getQuestions(query);
});
public SearchViewModel() {
mSearchRepository = new SearchRepository();
}
public void setQuery(String query) {
mSearchLiveData.setValue(query);
}
public LiveData<List<Question>> getQuestionLiveData() {
return mQuestionLiveData;
}
}
You'd then update your Activity to:
Always observe the getQuestionLiveData() (note that you won't get a callback to your Observer until you actually set the first query)
Call setQuery() on your SearchViewModel in your makeSearch()
Remove your CustomSearchViewModelFactory entirely (it would no longer be needed).
I am working on livedata. I have two apis but one api is dependent on another api. Based on first api response i am calling another api using livedata observer. I am calling from inside observer is this a right approach or any other alternative
mainViewModel.getListLiveData().observe(MainActivity.this, new Observer<List<Student>>() {
#Override
public void onChanged(#Nullable List<Student> list) {
if(list.size() > 0){
mainViewModel.getStudentLiveData().observe(MainActivity.this, new Observer<Student>() {
#Override
public void onChanged(#Nullable Student student) {
}
});
}
}
});
Observe on the student LiveData exposed by the mainViewModel. In the viewmodel make student live data change with change in List LiveData using Transformations or using a MediatorLiveData
In your activity:
mainViewModel.getStudentLiveData().observe(this, new Observer<Student>() {
student -> {}
});
In your viewmodel:
private MutableLiveData<List< Student>> studentListLiveData = new MutableLiveData(); // this will hold result of your first api call
private MutableLiveData<Student> studentLiveData = new MutableLiveData(); // this will hold result of your second api call
private void fetchData() {
fetchStudentList(new Callback {
result -> {
studentListLiveData.value = result;
fetchStudent(new Callback {
result -> { studentLiveData.value = result; }
});
}
})
}
public LiveData<Student> getStudentLiveData() {
return studentLiveData;
}
The Issue
I implemented the Android Architecture library and with this I am restoring data from Room Database, with MVVM (Model View View-Model) and whenever I use the observer to view LiveDada every time I start the app there is a noticeable delay when the items from the recycler view loads.
Image illustrates on what I mean when the app stats and the items show delay when they load.
What I want it to do
What I want to achieve with LiveData is this and the way I was able to achieve this is in my Dao I used a Query to get all of the data and pass it as a List rather than using LiveData and then in the Repository convert it to MutableLiveData and then pass it to database and from there observe it as LiveData in my fragment but using this approach actually doesn't update on delete or on insert unless if i restart the app.
Is there any way to fix this issue?
I would much like to use LiveData.
This is my DevicesDao interface:
#Dao
public interface DevicesDao {
#Insert
void insert(Devices... devices);
#Query("SELECT * FROM devices")
LiveData<List<Devices>> getDevices();
/*
#Query("SELECT * FROM devices")
List<Devices> getDevices();
*/
#Delete
void delete(Devices... device);
#Update
void update(Devices... device);
}
Database:
#Database(entities = {Devices.class}, version = 1)
public abstract class DevicesDatabase extends RoomDatabase {
private static final String DATABASE_NAME = "devices_registered";
private static DevicesDatabase instance;
public abstract DevicesDao devicesDao();
public static DevicesDatabase getInstance(final Context context) {
if (instance == null) {
synchronized (DevicesDatabase.class) {
if (instance == null) {
instance = Room.databaseBuilder(
context.getApplicationContext(),
DevicesDatabase.class,
DATABASE_NAME)
.fallbackToDestructiveMigration()
.build();
}
}
}
return instance;
}
}
Repository:
public class DevicesRepository {
private final DevicesDao devicesDao;
public DevicesRepository(Application application) {
DevicesDatabase db = DevicesDatabase.getInstance(application);
devicesDao = db.devicesDao();
}
public void addDevices(Devices devices) {
new InsertDeviceAsync(devicesDao).execute(devices);
}
public void updateDevice(Devices devices) {
new UpdateDeviceAsync(devicesDao).execute(devices);
}
public void deleteDevice(Devices devices) {
new DeleteDeviceAsync(devicesDao).execute(devices);
}
//Gets all data from SQLite
public LiveData<List<Devices>> getAllDevices() {
return devicesDao.getDevices();
}
/*
public LiveData<List<Devices>> getAllDevices() {
MutableLiveData<List<Devices>> devices = new MutableLiveData<>();
try {
devices.setValue(new GetDeviceAsync(devicesDao).execute().get());
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return devices;
}
*/
}
View Model File:
public class HomeFragmentViewModel extends AndroidViewModel {
private final DevicesRepository devicesRepository;
private LiveData<List<Devices>> devices;
public HomeFragmentViewModel(#NotNull Application application) {
super(application);
devicesRepository = new DevicesRepository(application);
devices = devicesRepository.getAllDevices();
}
public LiveData<List<Devices>> getAllDevices() {
return devices;
}
public void addNewDevice(Devices devices) {
devicesRepository.addDevices(devices);
}
public void deleteDevice(Devices devices) {
devicesRepository.deleteDevice(devices);
}
public void editDevice(Devices devices) {
devicesRepository.updateDevice(devices);
}
}
And lasty, the observer in my fragment:
///////Other code
//Implements ViewModel to HomeFragment
homeFragmentViewModel = ViewModelProviders.of(this).get(HomeFragmentViewModel.class);
homeFragmentViewModel.getAllDevices().observe(getViewLifecycleOwner(), devicesList -> {
//Validation tool
validationUtil = new ValidationUtil(devicesList);
//Adds to adapter
adapter.submitList(devicesList);
/////// Other code
});
Thank you for your time!!!
New update: When I reset the app data, it loads fine but as soon as I decide to update the code, the issue comes back regardless of that edit I make in Android Studio.