I'm following the ViewModel app architecture for android, and run in to a problem when retriving some simple data from the SQLite database. Here are the simplified version of my DAO, and my repo:
THE DAO:
#Dao
public interface UserStuffDao {
#Query("SELECT * from UserStuff")
List < UserStuff > getAllUsers();
#Query("SELECT path_to_user_profile_pic from UserStuff where id=:id")
LiveData < String > getPathToUserProfilePic(Long id);
}
THE REPO:
public class UserStuffRepository {
private static UserStuffRepository instance;
private static UserStuffDao userStuffDao;
public static final String TAG = UserStuffRepository.class.getSimpleName();
public static UserStuffRepository getInstance(Application application) {
Log.d(TAG, "[getInstance] called");
if (instance == null) {
instance = new UserStuffRepository();
Database db = Database.getDatabase(application.getApplicationContext());
userStuffDao = db.userStuffDao();
}
return instance;
}
public MutableLiveData < UserStuff > getUserStuff(Long id) {
final MutableLiveData < UserStuff > userData =
new MutableLiveData < > ();
LiveData < String > info = userStuffDao.getPathToUserProfilePic(id);
Log.d(TAG, "[getuserStuff] the dao returned ---> " + info.getValue());
UserStuff to_return = new UserStuff(id, info.getValue());
userData.setValue(to_return);
return userData;
}
public void geteverything() {
new AsyncTask < Void, Void, Void > () {
#Override
protected Void doInBackground(Void...voids) {
Log.d(TAG, " EVERYTHING IN THE DATABASE " + userStuffDao.getAllUsers());
return null;
}
}.execute();
}
}
Problem is when I call the method getUserStuff(Long id) from the repo, (which then calls the method from the DAO), I get a null (Log.d(TAG,"[getuserStuff] the DAO returned ---> "+info.getValue()); prints a null). I have a button that prints everything I have in the database, and there is clearly data in it, and the DAO should return something. Example:
Calling geteverything():
D/UserStuffRepository: EVERYTHING IN THE DATABASE [UserStuff{id=1, path_to_user_profile_pic='/path/path'}]
Then calling getUserStuff(1) returns a UserStuff with an id of 1, and a pathToUserProfilePic of null.
Do you see any clear problem, or should I post extra stuff like initializing the repo, the ViewModel, and some stuff from the MainActivity? Thanks!
Change your function to return a simple String
LiveData<String> getPathToUserProfilePic(Long id);
To this
String getPathToUserProfilePic(Long id);
And of course change how you get it
String info = userStuffDao.getPathToUserProfilePic(id);
Log.d(TAG," [getuserStuff] the dao returned ---> " + info);
I'm pretty sure you don't need a LiveData there because you save the String to UserStuff.
Related
I need to show a unit of measure from the product user selection and that when selecting it shows its price at the same time
Alert Dialog with data using floating btn
I don't know if it should be with the ViewModel or the repository in the second, because it must vary from the input of the first
I use the generic MVVM model pattern where I have my dao, entity (POJO) and ViewModel with Repo.
The other problem it's how show with Repository specific columns
I want to use specifics columns from my POJOS using method to string I created, not the toString override.
When this is ready I need this data inside the ActivityHost to check if is empty, don't send the data to the other fragments and the server.
In summary this is all the things:
the final display that I need
Please let me know if there is more data that I could provide.Thanks (I can't use Kotlin btw)
The result I want it's use the data to pass it to a RecycleView and then will go to my ActivityHost of fragmentProduct
This is part of the class:
Fragment Product
//initilization of variables,viewModels and Repos:
...
other functions
...
private void alertAddProducto() {
// build of the MaterialAlertDialog customView
final ArrayAdapter<Productos> dataAdapter = new ArrayAdapter<>(getActivity(),
R.layout.item_listado_layout, new ArrayList<>(0));
tiProducto.setAdapter(dataAdapter);
mProductosRepository.getAllProductosLiveData().observe(requireActivity(), items -> {
assert items != null;
Collections.sort(items, (obj1, obj2) -> obj1.getPRODUCTO_DESCRIPCION().compareToIgnoreCase(obj2.getPRODUCTO_DESCRIPCION()));
dataAdapter.addAll(items);
dataAdapter.notifyDataSetChanged();
});
//the other mRepo that I don't know if is necessary, (should be ViewModel)?
the second AutocompleteView needs the user input and then it should show other fields in my alert
like price ,quantity and subtotal (disabled)
}
ilustrative pictures
DAO
#Dao
public interface ProductosDao {
#Insert
void insert(Productos producto);
#Update
void update(Productos producto);
#Delete
void delete(Productos producto);
#Query("DELETE FROM productos_table")
void deleteAll();
#Query("SELECT * FROM productos_table ORDER BY PRODUCTO_ID DESC")
LiveData<List<Productos>> getAllProductosLiveData();
#Query("SELECT * FROM productos_table ")
LiveData<List<Productos>> getAllUnidadesMedida();
}
TABLE
#Entity(tableName = "productos_table")
public class Productos {
#PrimaryKey(autoGenerate = true)
private int AUTOGENERADO_ID;
private String PRODUCTO_DESCRIPCION;
...
public Productos(int AUTOGENERADO_ID,
...) {
this.AUTOGENERADO_ID = AUTOGENERADO_ID;
this.PRODUCTO_DESCRIPCION = PRODUCTO_DESCRIPCION;
}
...
#Override
public String toString() {
return PRODUCTO_ID + " " + PRODUCTO_DESCRIPCION;
}
public String getStringPRODUCTO_DESCRIPCION() {
return PRODUCTO_DESCRIPCION;
}
}
I want to use this methods not toString in AutocompleteView
ActivityHost
public class ActaHost extends DrawerBaseActivity{
//init variables
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityPacHostBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
FragmentManager fm = getSupportFragmentManager();
adapter = new ActaAdapter(fm, getLifecycle());
...
initViewPager2();
mSharedViewModel = new ViewModelProvider(this).get(ActaSharedViewModel.class);
observable(mSharedViewModel);
}
observable(SharedViewModel mSharedViewModel){
mProductoViewModel.getAllProductos().observe(this, Productos-> {
assert Productos!= null;
Collections.sort(Productos, (obj1, obj2) -> obj1.getProducto().compareToIgnoreCase(obj2.getProducto()));
for (int i = 0; i < Productos.size(); i++) {
ProductosList.add(Productos.get(i));
Log.i("ProductosList", Productos.get(i) + String.valueOf(ProductosList.get(i).getProducto()));
}
Log.i("ProductosList", ProductosList.toString());
});
}
}
I am trying to run a Get method in java Restful API. I have tried naming my model classes similar to my real-time firebase. I have tried using interface callback, and it has printed to my console but it still return null in my postman,
My interface callback class
public interface CallBack {
void onCallBack(DeviceTest value);
}
my model class
public class DeviceTest implements Serializable {
private String LightSwitch;
private String DoorSwitch;
// empty COnstructor
public DeviceTest() { }
public DeviceTest(String lightSwitch, String doorSwitch) {
this.LightSwitch = lightSwitch;
this.DoorSwitch = doorSwitch;
}
public String getLightSwitch() {
return LightSwitch;
}
public void setLightSwitch(String lightSwitch) {
LightSwitch = lightSwitch;
}
public String getDoorSwitch() {
return DoorSwitch;
}
public void setDoorSwitch(String doorSwitch) {
DoorSwitch = doorSwitch;
}
#Override
public String toString() {
return "DeviceTest{" +
"LightSwitch='" + LightSwitch + '\'' +
", DoorSwitch='" + DoorSwitch + '\'' +
'}';
}
}
The FireBase Connection class and the method I use.
public static void handleLight(CallBack callback) {
FireBaseService fbs = null;
fbs = new FireBaseService();
device = new DeviceTest();
mylist = new ArrayList<Map<String, Object>>();
DatabaseReference ref = fbs.getDb()
.getReference("/Devices/Lamp/Ambient");
Map<String, Object> chemainChild = new HashMap<>();
// chemainChild.put("server/user/",array);
ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
callback.onCallBack(dataSnapshot.getValue(DeviceTest.class));
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
//mylist.add(device);
}
The Get method i use to print to postman
DeviceTest deviceTest = new DeviceTest();
#GET
#Produces(MediaType.APPLICATION_JSON)
public DeviceTest getCustomers() {
//System.out.println(device.toString());
FireBaseService.handleLight(new CallBack() {
#Override
public void onCallBack(DeviceTest value) {
// this one will print to my console
System.out.println("Should work " + value.toString());
// this value does not seem to be added to my deviceTest, likely a asynchronous issue again.
deviceTest = new DeviceTest(value.getLightSwitch(),value.getDoorSwitch());
}
});
// this is here i get my null value
return deviceTest ;
}
This is what i got in my postman:
What i got in my console:
My question is how can I print this value DeviceTest{LightSwitch='LIGHT', DoorSwitch='CLOSED'} in my console to my postman? The issue seems to be in my GET Method.
You're right, sorry for misunderstanding the question.
It is because you return deviceTest immediately before its actually value was resolved.
You probably want to use a CompletableFuture.
Try something like this:
CompletableFuture<DeviceTest> deviceTestFut = new CompletableFuture<>();
//System.out.println(device.toString());
FireBaseService.handleLight(new CallBack() {
#Override
public void onCallBack(DeviceTest value) {
// this one will print to my console
System.out.println("Should work " + value.toString());
// this value does not seem to be added to my deviceTest, likely a asynchronous issue again.
deviceTestFut.complete(new DeviceTest(value.getLightSwitch(),value.getDoorSwitch());
})
});
// this is here i get my null value
return deviceTestFut;
The you can call:
deviceTestFut.thenAccept(value -> System.out.println("Should work " + value.toString()););
Solved
Edit my GET Method to this
CompletableFuture<DeviceTest> deviceTestFut = new CompletableFuture<>();
//System.out.println(device.toString());
FireBaseService.handleLight(new CallBack() {
#Override
public DeviceTest onCallBack(DeviceTest value) {
deviceTest = new DeviceTest(value.getLightSwitch(),value.getDoorSwitch());
System.out.println("Here is the value" + deviceTest.toString());
deviceTestFut.complete(new DeviceTest(value.getLightSwitch(),value.getDoorSwitch()));
return deviceTest;
}
});
// this is here i get my null value
deviceTestFut.thenAccept(value -> System.out.println("Should work " + value.toString()));
return deviceTestFut.get();
}
I'm just trying to learn mvvm, and I faced some issue - My List which should contain response from API are empty. I'm not sure why it is. Here's some code:
MainActivity
mViewModel = new ViewModelProvider(this).get(ViewModel.class);
mViewModel.getData().observe(this, new Observer<List<Model>() {
#Override
public void onChanged(List<Model> list) {
if (data.size() > 0) {
data.clear();
}
if (list != null) {
data.addAll(list);
Log.i(TAG, "onChanged: " + data.size());
}
}
});
ViewModel
private Repository mRepository;
private MutableLiveData<List<Model> liveData;
public ViewModel(#NonNull Application application) {
super(application);
mRepository = Repository.getInstance();
liveData = mRepository.getData();
}
public MutableLiveData<List<Model> getData(){
return liveData;
}
Repository
public MutableLiveData<List<Model>> getData(){
MutableLiveData<List<Model> mLiveData = new MutableLiveData<>();
mApiCall.callApi()
.enqueue(new Callback<List<Model>() {
#Override
public void onResponse(Call<List<Model> call, Response<List<Model> response) {
mLiveData.setValue(response.body());
}
#Override
public void onFailure(Call<List<Model> call, Throwable t) {
t.getMessage();
}
});
return mLiveData;
}
in function getData() you are returning a live data and in this line in your view model :
liveData = mRepository.getData();
you are assigning it to liveDate which is a mutable live data created in your view model and the problem is here.
when this assignment is happened the observer in the liveData variable in view model will be removed and that's why we should use switchMap like this :
private var result : LiveData<List<Response>> = MutableLiveData()
result : LiveData<Response> = Transformations.map(mRepository.getData()){
it
}
and now all you need to do is to observe on repo in your view like this :
viewmodel.result.observe(this, Observer{ list ->
// to do with the result
})
response.body() will provide with class APIResponse. But what you need as response is List.
To get the expected response as List, try response.body().getMetadata().getResults()
It could be either repository initialisation as well. Do try creating
public void init() {
mRepository = Repository.getInstance();
liveData = mRepository.getData();
}
use viewModel.init();
Model class properties should match with response JSON fields to get the model objects from the response.
Debug on the response.body() and see the data coming. if the model is empty then there would be a mismatch on the fields.
I created a room database following this guide from code labs It makes use of a repository to:
A Repository manages query threads and allows you to use multiple backends. In the most common example, the Repository implements the logic for deciding whether to fetch data from a network or use results cached in a local database.
I followed the guide and i'm now able to create the entity's & retrieve the data. I even went further and created another whole entity outside the scope of the guide.
However I can't find many resources that use this MVVM(?) style so am struggling as to really under stand the repository. For now I want to update a field. Just one, as if I am able to manage that the rest should be similar.
I want to update a field called dartshit and I have the dao method created for this:
#Query("UPDATE AtcUserStats SET dartsHit = :amount WHERE userName = :userName")
void UpdateHitAmount(int amount, String userName);
I have one repository which I assumed I use for all entities:
public class UsersRepository {
private UsersDao mUsersDao;
private AtcDao mAtcDao;
private LiveData<List<Users>> mAllUsers;
private LiveData<List<AtcUserStats>> mAllAtc;
private AtcUserStats mAtcUser;
UsersRepository(Application application) {
AppDatabase db = AppDatabase.getDatabase(application);
mUsersDao = db.usersDao();
mAtcDao = db.atcDao();
mAllUsers = mUsersDao.fetchAllUsers();
mAllAtc = mAtcDao.getAllAtcStats();
}
LiveData<List<Users>> getAllUsers() {
return mAllUsers;
}
LiveData<List<AtcUserStats>> getAllAtcStats() {
return mAllAtc;
}
LiveData<AtcUserStats> getAtcUser(String username) {
return mAtcDao.findByName(username);
}
public void insert (Users user) {
new insertAsyncTask(mUsersDao).execute(user);
}
public void insertAtc (AtcUserStats atc) {
new insertAsyncAtcTask(mAtcDao).execute(atc);
}
private static class insertAsyncTask extends AsyncTask<Users, Void, Void> {
private UsersDao mAsyncTaskDao;
insertAsyncTask(UsersDao dao) {
mAsyncTaskDao = dao;
}
#Override
protected Void doInBackground(final Users... params) {
mAsyncTaskDao.insertNewUser(params[0]);
return null;
}
}
private static class insertAsyncAtcTask extends AsyncTask<AtcUserStats, Void, Void> {
private AtcDao mAsyncTaskDao;
insertAsyncAtcTask(AtcDao dao) {
mAsyncTaskDao = dao;
}
#Override
protected Void doInBackground(final AtcUserStats... params) {
mAsyncTaskDao.insertNewAtcUser(params[0]);
return null;
}
}
}
My question is how do I create a AsyncTask for the update query I am trying to run in this repository?
Here is what I have so far by broadly copying the insert repository methods:
private class updateHitAsyncTask {
private AtcDao mAsyncTaskDao;
public updateHitAsyncTask(AtcDao mAtcDao) {
mAsyncTaskDao = mAtcDao;
}
protected Void doInBackground(int amount, String name) {
mAsyncTaskDao.UpdateHitAmount(amount, name);
return null;
}
}
Which is incorrect is that I'm getting a llegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time. error. But i thought this AsyncTask is suppose to take care of this?
Here is my update method in my view model, which is reporting 0 errors:
void updateHitAmount (int amount, String name) {
mRepository.updateAtcHits(amount, name);
}
and here is the UI code where im actually trying to tie all these together, I suspect there must be a better way that using onChanged for simply updating a field but again I am struggling to come across any advice on google with the repository approach:
private void callOnChanged() {
mAtcViewModel = ViewModelProviders.of(this).get(AtcViewModel.class);
mAtcViewModel.getAllUsers().observe(this, new Observer<List<AtcUserStats>>() {
#Override
public void onChanged(#Nullable final List<AtcUserStats> atc) {
// Update the cached copy of the users in the adapter.
for (int i = 0; i < atc.size(); i++) {
if (atc.get(i).getUserName().equals(mUser)) {
mAtcViewModel.updateHitAmount(55, mUser);
//atc.get(i).setDartsHit(55);
Log.d("id", String.valueOf(userSelected.getId()));
}
}
}
});
How can I update fields using this approach on the background thread?
Figured it out due to this answer here. It was mostly because of my lack of understanding of AsyncTask. Essentially I needed to create an object and pass the data that way and then execute in the background:
private static class MyTaskParams {
int amount;
String name;
MyTaskParams(int amount, String name) {
this.amount = amount;
this.name = name;
}
}
public void updateAtcHits (int amount, String name) {
MyTaskParams params = new MyTaskParams(amount,name);
new updateHitAsyncTask(mAtcDao).execute(params);
}
private class updateHitAsyncTask extends AsyncTask<MyTaskParams,Void,Void>{
private AtcDao mAsyncTaskDao;
public updateHitAsyncTask(AtcDao mAtcDao) {
mAsyncTaskDao = mAtcDao;
}
#Override
protected Void doInBackground(MyTaskParams... myTaskParams) {
int amount =myTaskParams[0].amount;
String name = myTaskParams[0].name;
mAsyncTaskDao.UpdateHitAmount(amount, name);
return null;
}
}
I have a variable that gets the instance of a database (ROOM) and does a select, returning object, CidVo.
private AppDataBase appDataBase;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_atendimento_tab);
appDataBase = AppDataBase.getInstance(this);
}
In a return of an asynchronous task, I make this query. I need the CidVo corresponding to what was returned to me by the asynchronous task, the CidVo's are recorded in the database and yes, there are correspondences of them in that base.
The problem is in the second iteration onwards of the FOR loop.
Why does it happen?
For loop:
#Override
public void retornoAsyncTaskResultPredict(AsyncTaskResult<Retorno> asyncTaskResult) {
if (asyncTaskResult.getExceptionResult() == null) {
RetornoPredicaoCid predicaoCidVo = (RetornoPredicaoCid) asyncTaskResult.getResult();
if (predicaoCidVo.getRetorno() != null) {
List<PredicaoCidVo> predCid = predicaoCidVo.getRetorno();
List<CidVo> predictedText = new ArrayList<>();
List<CidVo> predictedText1 = new ArrayList<>();
for (int i = 0;i<predCid.size();i++) {
//appDataBase = AppDataBase.getInstance(this);
CidVo cidVo = appDataBase.getCidDao().getCidVo(predCid.get(i).getTextPredicted());
CidVo cidVo1 = appDataBase.getCid(this, predCid.get(i).getTextPredicted());
predictedText1.add(cidVo1);
predictedText.add(cidVo);
}
}
}
AppDataBase:
#Database(entities = {CidVo.class}, version = 8, exportSchema = false)
public abstract class AppDataBase extends RoomDatabase {
public abstract CidDao getCidDao();
private static AppDataBase appDataBase;
public static AppDataBase getInstance(Context context) {
if (null == appDataBase) {
appDataBase = buildDataBaseInstance(context);
}
return appDataBase;
}
private static AppDataBase buildDataBaseInstance(Context context) {
return Room.databaseBuilder(context,
AppDataBase.class,
"AutoCompleteVo")
.fallbackToDestructiveMigration()
.allowMainThreadQueries().build();
}
//CID predicted
public CidVo getCid(Context context, String idCidVo) {
if (appDataBase == null) {
appDataBase = AppDataBase.getInstance(context);
}
return appDataBase.getCidDao().getCidVo("%" + idCidVo + "%");
}
}
DAO:
#Dao
public interface CidDao {
//CID
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAllCID(List<CidVo> cidVos);
#Query("Select * from CidVo WHERE idCid = :idCid")
CidVo getCidVo(String idCid);
}
The first iteration of the loop returns normally, but from the second on, returns null, the select.
Edit:
Edit:
If I select the variable (appdatabase) (ALT + F8) and place the code path manually, the query returns the data to me.
In the second iteration on, the items came to come with a space at the beginning or end, causing the problem. Solved with a simple .trim();
predCid.get(i).getTextPredicted ().trim()