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());
});
}
}
Related
Since I'm new to programming Android apps I followed a tutorial on how to use the Android Architecture Components and Firebase for implementing the MVVM (using LiveData, ViewModel, etc.).
The tutorial I followed can be found here:
Part 1
Part 2
Part 3
I'm now left with what I think is a decent implementation of the MVVM, but I can not wrap my head around how I am supposed to pass query parameters to it. Right now I need to hardcode the ID of the document I want to retrieve:
public class AlarmDAO {
private FirebaseFirestore firebaseFirestore = FirebaseFirestore.getInstance();
public AlarmLiveData getFirestoreLiveData() {
DocumentReference documentReference = firebaseFirestore.collection(Collection.ALARMS.name).document("5RxJNuNyhDJlz49wpBkw");
return new AlarmLiveData(documentReference);
}
}
That then gets called by a class extending ViewModel.
public class AlarmViewModel extends ViewModel {
private AlarmDAO DAO = new AlarmDAO();
private AlarmLiveData liveData = null;
public LiveData<Alarm> getAlarmLiveData() {
liveData = DAO.getFirestoreLiveData();
return liveData;
}
public LiveData<Alarm> getAlarm() {
return liveData.alarm;
}
}
And then I observe that data in my activity:
model.getAlarmLiveData().observe(this, Observable -> {});
model.getAlarm().observe(this, alarm -> {
if (alarm != null) {
alarmTextView.setText(alarm.getTest());
else {
Log.d(TAG, "Waiting for data");
}
});
My problem is that I do not see a way of querying for a specific alarm. For instance model.getAlarm("someId"). I am under the impression that it should be done in the DAO and/or the ViewModel, but I can't figure out how. Another thing I do not understand is why I need to observe both model.getAlarmLiveData() and model.getAlarm() in my activity, as using only one does not work. The answer to both of those questions is most likely very simple, but thus far I haven't been able to figure it out.
For completeness: the Alarm class is nothing besides a getter and setter for two strings, and the AlarmLiveData class is below.
public class AlarmLiveData extends LiveData<Alarm> implements EventListener<DocumentSnapshot> {
private static final String TAG = AlarmLiveData.class.getSimpleName();
private Alarm alarmTemp = new Alarm();
private DocumentReference documentReference;
private ListenerRegistration listenerRegistration = () -> {};
public MutableLiveData<Alarm> alarm = new MutableLiveData<>();
public AlarmLiveData(DocumentReference documentReference) {
this.documentReference = documentReference;
}
#Override
protected void onActive() {
listenerRegistration = documentReference.addSnapshotListener(this);
super.onActive();
}
#Override
protected void onInactive() {
listenerRegistration.remove();
super.onInactive();
}
#Override
public void onEvent(#Nullable DocumentSnapshot documentSnapshot, #Nullable FirebaseFirestoreException e) {
if (documentSnapshot != null && documentSnapshot.exists()) {
alarmTemp = new Alarm();
alarmTemp.setId(documentSnapshot.getId());
alarmTemp.setTest(documentSnapshot.get("test").toString());
alarm.setValue(alarmTemp);
} else {
Log.d(TAG, "ERROR");
}
}
}
Thank you for reading, I'm looking forward to the answer(s)!
The reason you have to use both model.getAlarmLiveData() and model.getAlarm() looks to be that your AlarmLiveData class extends LiveData but sets a value for the contained MutableLiveData member variable instead of setting its own class value.
Inside your AlarmLiveData class:
// Comment out/Remove your 'public MutableLiveData<alarm> alarm' member variable from the top.
// You're going to want to set the value of the AlarmLiveData class itself instead.
// ...
// Then inside of your onEvent callback
#Override
public void onEvent(#Nullable DocumentSnapshot documentSnapshot, #Nullable FirebaseFirestoreException e) {
if (documentSnapshot != null && documentSnapshot.exists()) {
alarmTemp = new Alarm();
alarmTemp.setId(documentSnapshot.getId());
alarmTemp.setTest(documentSnapshot.get("test").toString());
// Set the value for the AlarmLiveData class directly
setValue(alarmTemp);
} else {
Log.d(TAG, "ERROR");
}
}
I'm not sure why you're creating a DAO class and I would most likely move that code directly into the AlarmViewModel class.
But, here is how you can alter your current DAO class if you don't want to remove it:
// Pass in the document id you want to create a document reference for
public AlarmLiveData getFirestoreLiveData(String documentId) {
DocumentReference documentReference = firebaseFirestore.collection(Collection.ALARMS.name).document(documentId);
return new AlarmLiveData(documentReference);
}
Your AlarmViewModel class would look something like this:
public class AlarmViewModel extends ViewModel {
private AlarmDAO DAO = new AlarmDAO();
private AlarmLiveData liveData = null;
// Make sure to take in the document id so you can create the corresponding LiveData
public LiveData<Alarm> getAlarmLiveData(String documentId) {
// Only create a new LiveData instance if the current one is null.
// This is helpful if you intend to use this as a Shared ViewModel.
if(liveData == null){
liveData = DAO.getFirestoreLiveData(documentId);
}
return liveData;
}
}
Finally, in your Activity:
// Pass in the document id and observe the ViewModel
model.getAlarmLiveData("MY_DOCUMENT_ID").observe(this, alarm -> {
if (alarm != null) {
alarmTextView.setText(alarm.getTest());
}else{
Log.d(TAG, "Waiting for data");
}
});
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.
In my application, I should use Material Stepper and for this, I want to use this library : https://github.com/ernestoyaquello/VerticalStepperForm
But I want to add this dynamically from server.
For connecting with server I used Retrofit library and I should check the type of items from server.
when this type is "penny" show one of this steps and when the type is "best" show another step.
I create this steps from library tutorials, but i want when type is penny show me StepDynamicTxt and when the type is best show me StepDynamicEdt!
I write below codes but just add one of the items from each step!
But in API, I have 2 item of penny types and 3 items of best type!
Should show me 5 step, but show me 2 step!
My codes :
public class StepperActivity extends AppCompatActivity {
private ApiServices apiServices;
private ProgressBar loader;
private VerticalStepperFormView stepper;
private StepDynamicEdt stepDynamicEdt;
private StepDynamicTxt stepDynamicTxt;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bidzila_stepper);
//Initialize
apiServices = ApiClient.ApiClient().create(ApiServices.class);
loader = findViewById(R.id.bidStepper_loader);
stepper = findViewById(R.id.bidStepper);
//Api
callAPi();
}
private void callAPi() {
loader.setVisibility(View.VISIBLE);
Call<TodayResponse> call = apiServices.TODAY_RESPONSE_CALL(5);
call.enqueue(new Callback<TodayResponse>() {
#Override
public void onResponse(Call<TodayResponse> call, Response<TodayResponse> response) {
if (response.isSuccessful()) {
if (response.body() != null) {
if (response.body().getRes() != null) {
if (response.body().getRes().getToday().size() > 0) {
loader.setVisibility(View.GONE);
//Foreach
for (int i = 0; i < response.body().getRes().getToday().size(); i++) {
if (response.body().getRes().getToday().get(i).getType().equals("penny")) {
stepDynamicEdt = new StepDynamicEdt(response.body().getRes().getToday().get(i).getName());
} else if (response.body().getRes().getToday().get(i).getType().equals("best")) {
stepDynamicTxt = new StepDynamicTxt(response.body().getRes().getToday().get(i).getName());
}
}
stepper.setup(new StepperFormListener() {
#Override
public void onCompletedForm() {
}
#Override
public void onCancelledForm() {
}
}, stepDynamicEdt, stepDynamicTxt)
.allowNonLinearNavigation(false)
.displayCancelButtonInLastStep(false)
.displayBottomNavigation(false)
.confirmationStepTitle("Confirm")
.stepNextButtonText("Continue")
.lastStepNextButtonText("Finish")
.includeConfirmationStep(false)
.init();
}
}
}
}
}
#Override
public void onFailure(Call<TodayResponse> call, Throwable t) {
Log.e("ResponseErr", t.getMessage());
}
});
}
#Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(ViewPumpContextWrapper.wrap(newBase));
}
}
I think this problem for this line:}, stepDynamicEdt, stepDynamicTxt) because just add 2 step.
How can i add this step dynamically in Android?
In your code, you are making a very fundamental mistake. And that is, you are using the same variable each time in your loop to store dynamic edit type and dynamic text type, which will replace any previously created fields. And hence when you finally create them, you end up with single last values of each type.
What you can do is, create a List with type Step, add new type every time you get them, and finally pass that list to the builder.
The builder accepts a list too, you should check implementation when its open source.
// before the for loop, create a list of type Step
List<Step> steps = new ArrayList();
// your loop on response received from server
for (int i = 0; i < response.body().getRes().getToday().size(); i++) {
if (response.body().getRes().getToday().get(i).getType().equals("penny")) {
StepDynamicEdt stepDynamicEdt = new StepDynamicEdt(response.body().getRes().getToday().get(i).getName());
// add to list
steps.add(stepDynamicEdt);
} else if (response.body().getRes().getToday().get(i).getType().equals("best")) {
StepDynamicTxt stepDynamicTxt = new StepDynamicTxt(response.body().getRes().getToday().get(i).getName());
// add to list
steps.add(stepDynamicTxt);
}
}
// finally create them
stepper.setup(new StepperFormListener() {
#Override
public void onCompletedForm() {
}
#Override
public void onCancelledForm() {
}
}, steps) // pass the list
.allowNonLinearNavigation(false)
.displayCancelButtonInLastStep(false)
.displayBottomNavigation(false)
.confirmationStepTitle("Confirm")
.stepNextButtonText("Continue")
.lastStepNextButtonText("Finish")
.includeConfirmationStep(false)
.init();
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.
UPDATE:::
I've updated the question to include demo other LiveData that were also required:
so we have userLD that we need the value of to get the goalWeeklyLD, and we need the goalWeeklyLD value to get the remaining 4 LiveData values as they come from Room querys that use goalWeekly.dateproperties in the query
:::::
I've hit a problem where I have a fragment that has to populate LiveData that uses a query dependent on another LiveData value.
how can i get my live data to work correctly when it is dependent on other results?
Without using The Transitions.map() the view model throws an error because the values of the other live data are still null.
with the Transitions.map() in the view model the activities observer throws an error because the LiveData is still null.
I could possibly cheat my way past this by using a horrendously big nested query to return all i need in one custom DTO. but i'd rather understand whats going on here and how to handle this sort of situation properly.
Hopefully some code will make this clear
The Activity:
public class SomeFragment extends Fragment {
public static SomeFragment newInstance() {
return new SomeFragment();
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
someViewModel = ViewModelProviders.of(this).get(SomeViewModel.class);
//getting user details from previous activity
Intent intent = getActivity().getIntent();
if (intent != null){
if (intent.hasExtra(USER_ID)){
user = new User(intent.getStringExtra(USERNAME));
user.setId(intent.getLongExtra(USER_ID,0));
someViewModel.setUserLD(user);
}
}
someViewModel.getUserLD().observe(this, new Observer<User>() {
#Override
public void onChanged(#Nullable User userVal) {
user = userVal;
}
});
someViewModel.getGoalWeeklyLD().observe(this, new Observer<User>() {
#Override
public void onChanged(#Nullable User userVal) {
user = userVal;
}
});
//the below Observer calls throw an error because LiveData is null. makes sense.
//but how can i say "don't try and observe these until the transition.map has ran (because then it wont be null after if my understanding is right)" or something to that effect
someViewModel.getFirstLD(user.getId()).observe(this, new Observer<XObject>() {
#Override
public void onChanged(#Nullable Grades avgSportGradeVal) {
//Update UI
}
});
someViewModel.getSecondLD(user.getId()).observe(this, new Observer<XObject>() {
#Override
public void onChanged(#Nullable Grades avgBoulderGradeVal) {
// Update UI
}
});
someViewModel.getThriLD(user.getId()).observe(this, new Observer<XObject>() {
#Override
public void onChanged(#Nullable Grades avgBoulderGradeVal) {
// Update UI
}
});
someViewModel.getFourthLD(user.getId()).observe(this, new Observer<XObject>() {
#Override
public void onChanged(#Nullable Grades avgBoulderGradeVal) {
// Update UI
}
});
}}
The View Model:
public class SomeViewModel extends AndroidViewModel {
DaoRepository daoRepository;
MutableLiveData<User> userLD;
LiveData<XObject> firstLD;
LiveData<XObject> secondLD;
LiveData<XObject> thirdLD;
LiveData<XObject> fourthLD;
public MutableLiveData<User> getUserLD() {
return userLD;
}
public void setUserLD(User user){
userLD.setValue(user);
}
public LiveData<XObject> getFirstLD(long userId) {
return goalWeeklyLD;
}
public LiveData<XObject> getSecondLD(long userId) {
return goalWeeklyLD;
}
public LiveData<XObject> getThirdLD(long userId) {
return goalWeeklyLD;
}
public LiveData<XObject> getForthLD(long userId) {
return goalWeeklyLD;
}
public SomeViewModel(#NonNull Application application) {
super(application);
daoRepository = new DaoRepository(application);
userLD = new MutableLiveData<>();
//so the first LiveData waits for the user to be populated before getting its LiveData becasue we need the userId for our Room query to run
firstLD = Transformations.map(userLD, user -> daoRepository.getMostRecentGoalWeekly(user.getId()).getValue());
//the remaining live data uses values from the first...
setupOtherTransformMaps(userLD.getValue())
}
public void setupOtherTransformMaps(long userId) {
//the secondLD, thirdLD and fourthLD all depends on values from the first (in runs a query that uses its dateExpired)
secondLD = Transformations.map(firstLD, first ->
daoRepository.getAvgGradeRouteInPeriod(userId, first.getDateCreated(),first.getDateExpires()).getValue());
thirdLD = Transformations.map(firstLD, first ->
daoRepository.getAvgGradeRouteInPeriod(userId, first.getDateCreated(),first.getDateExpires()).getValue());
fourthLD = Transformations.map(firstLD, first ->
daoRepository.getAvgGradeRouteInPeriod(userId, first.getDateCreated(),first.getDateExpires()).getValue());
}}
Thankfully Google was smart and created a component which lets you combine variable number of LiveData into a single LiveData, and only emit events when you choose to do so!
This is called MediatorLiveData.
In your case though, you only need to channel 1 LiveData (userLD) into 1 another LiveData, that will emit each time userLd has a new value.
So you can use a predefined MediatorLiveData that does exactly this, specifically Transformations.switchMap.
firstLD = Transformations.switchMap(userLD, user -> daoRepository.getMostRecentGoalWeekly(user.getId()));
EDIT: Yup, you seem to need to expose these LiveData separately from one another, but they all depend on the first query to execute.
So you need to replace Transformations.map { ...getValue() with Transformations.switchMap and you'll be good to go.
public SomeViewModel(#NonNull Application application) {
super(application);
CustomApplication app = (CustomApplication) application;
daoRepository = app.daoRepository();
userLD = new MutableLiveData<>();
firstLD = Transformations.switchMap(userLD, user -> daoRepository.getMostRecentGoalWeekly(user.getId()));
secondLD = Transformations.switchMap(firstLD, first ->
daoRepository.getAvgGradeRouteInPeriod(userId, first.getDateCreated(),first.getDateExpires()));
thirdLD = Transformations.switchMap(firstLD, first ->
daoRepository.getAvgGradeRouteInPeriod(userId, first.getDateCreated(),first.getDateExpires()));
fourthLD = Transformations.switchMap(firstLD, first ->
daoRepository.getAvgGradeRouteInPeriod(userId, first.getDateCreated(),first.getDateExpires()));
}