My app fetches Top Rated or Most Popular movies from themoviedb.org.
I'm trying to implement ViewModel with LiveData to toggle these two buttons. In MainViewModel.java, I have this:
//private static long String TAG = MainViewModel.class.getSimpleName();
public LiveData<List<MovieRoom>> movies;
private AppDatabase database;
/*
Use constructor to initialize all data that UI needs to populate
*/
public MainViewModel(#NonNull Application application) {
super(application);
// how do I know this was initialized correctly?
database = AppDatabase.getInstance(this.getApplication());
//database = AppDatabase.getInstance(application);
//movies is null after calling ViewModelProvider constructor
// what are the values in movies?
// _movieDao = null, mDatabase = null
movies = database.movieDao().getAllMovies();
}
// Loads most popular movies
public void loadData() {
//MainViewModel.FetchMovieTask movies = new MainViewModel.FetchMovieTask();
//movies.execute("popular");
// Assign to movies
}
public void getAllMovies() {
movies = database.movieDao().getAllMovies();
}
public void getPopularMovies() {
movies = database.movieDao().getPopularMovies();
}
public void getTopRatedMovies() {
movies = database.movieDao().getTopRatedMovies();
}
public void getFavoriteMovies() {
movies = database.movieDao().loadFavoriteMovies(true);
}
}
In my MainActivity.java, the buttons have an onclick listener and I have set the observer as the following:
b_pop.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//updateMovies("popular");
mViewModel.getPopularMovies();
}
});
mViewModel.movies.observe(this, new Observer<List<MovieRoom>>() {
#Override
public void onChanged(#Nullable List<MovieRoom> movieEntries) {
//Log.d(TAG, "Receiving database update from LiveData");
// mMovieAdapter.mMovies = 0
mMovieAdapter.clear();
// movieEntries = 0
// mMovieAdapter mMovies = 0
mMovieAdapter.setList(movieEntries);
mMovieAdapter.notifyDataSetChanged();
}
});
Problem is, when you reassign the ViewModel's "movies" variable with top rated or most popular list of movies, it doesn't trigger onChanged(). The Top Rated list of movies is the same list of movies as the most popular, but just in different order. So onChanged() is never triggered and I will not be able to set it to the adapter.
I'm learning ViewModel, LiveData and observer from 0 so not sure how to redesign this so that it can work?
Pass the value of newly fetched data to the ViewModel using value on your LiveData:
public void getAllMovies() {
movies.setValue(database.movieDao().getAllMovies().getValue())
}
Also you need MutableLiveData
public MutableLiveData<List<MovieRoom>> movies;
Related
Here is the thing that I need to do.
When the user click on a button on an activity , the app must call a function in different class and sent back a notification to the activity. Then the activity shows those information in the main screen.
(Let's say the function is to receive firebase data and add it to a sqlite database. Once the data retrieval is complete ,I want to populate those information in the activity )
Is there any way to do this without using Room database
Currently I am writing the method in the activity class and redirect from there to populate data. Here is a example how I currently use it
final FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("server/saving-data/fireblog/posts");
ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
int counter = dataSnapshot.size();
for(int i =0; i<counter;i++){
Post post = dataSnapshot.getValue(Post.class);
// Add to sqlite data
if(i=counter) {
// Populate Data in activity
}
}
}
The Thing i want to do it ,I want to take this function code to a sepeate class and run from the activity and get a callback.
I am a newbie and don't have a idea how to do this. Thank you
I found a solution myself.
first you need to create interface , as an example let's take this >
public interface ActionListenerCallback {
public void onActionSuccess(String successMessage);
public void onActionFailure(Throwable throwableError);
}
After that you need to implement this in the activity where you called the function
public class Act_Reps extends AppCompatActivity implements ActionListenerCallback {
#Override
protected void onCreate(Bundle savedInstanceState) {
RealtimeDB RDB= new RealtimeDB(this,Id);
RDB.setCallback(this);
RDB.callingFunction();
}
#Override
public void onActionSuccess(String successMessage) {
Log.d("Log", "Message "+ successMessage);
}
#Override
public void onActionFailure(Throwable throwableError) {
}
}
Here is the class where the function is called
public class RealtimeDB {
ActionListenerCallback callback;
public void setCallback(ActionListenerCallback callback) {
this.callback = callback;
}
public void callingFunction(){
handler.postDelayed(new Runnable() {
#Override
public void run() {
callback.onActionSuccess("Done");
}
}, 5000);
}
}
I have recyclerview with each item holding some buttons and text.
I am setting my onViewClickListener in the ViewHolder. I have a Room database initialized in the MainActivity.
In terms of good app architecture, how should I change the data in my database once my OnClickListener triggers?
Do I pass the database as a parameter to the adapter then the view holder?
OR
Do I get the database through a getDatabase method that I implement?
What is the best way to go about this? I am open to any suggestion/design pattern.
How is something like this generally handled?
Best practise is to keep the database in your AppCompatActivity.
If you display database data in that recyclerView you should create an ArrayList filled with those data and pass it to the recyclerViewAdapter in the constructor like MyRecyclerViewAdapter myRecyclerViewAdapter = new MyRecyclerViewAdapter(myData)
Then everytime you change something from that list (from your activity) you just call notify method based on your action. General one is myRecyclerViewAdapter.notifyDataSetChanged(). But with lot data it's more efficient to use more specific notify methods.
And if the database should change based on some event in recyclerView, for example by the onViewClickListener. You should create an interface in which you would pass the change and then apply the change to the database in your AppCompatActivity.
You should keep your database (and strongly advise you to use the data with ViewModel) in your activity. You can make changes on data of recyclerview item by using weakreference and interface. Here is example of activity and adapter.
public class MyActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private MyObjectAdapter adapter;
private final List<MyObject> myObjectList = new ArrayList<>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
//define your views here..
setAdapter();
getData();
}
private void setAdapter(){
adapter = new MyObjectAdapter(myObjectList, new
MyObjectAdapter.IObjectClickListener() {
#Override
public void onButtonOneClick(MyObject myObject) {
//do button one operation with object item;
}
#Override
public void onButtonTwoClick(MyObject myObject) {
//do button two operation with object item;
}
});
}
private void getData(){
//Get your data from database and pass add them all into our
//myObjectList
myObjectList.addAll(objects); //Suppose objects come from database
adapter.notifyDataSetChanged();
}
}
And adapter class is like
public class MyObjectAdapter extends RecyclerView.Adapter<MyObjectAdapter.ObjectViewHolder> {
private final List<MyObject> myObjects;
private final IObjectClickListener listener; //defined at the bottom
public MyObjectAdapter(List<MyObject> myObjects, IObjectClickListener listener) {
this.myObjects = myObjects;
this.listener = listener;
}
/*
..
Other methods of adapter are here
..
*/
public class ObjectViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
private Button buttonOne, buttonTwo;
private WeakReference<IObjectClickListener> reference;
public ObjectViewHolder(#NonNull View itemView) {
super(itemView);
//define your views here
reference = new WeakReference<>(listener);
buttonOne.setOnClickListener(this);
buttonTwo.setOnClickListener(this);
}
#Override
public void onClick(View v) {
if (v == buttonOne){
reference.get().onButtonOneClick(myObjects.get(getAdapterPosition()));
} else if (v == buttonTwo){
reference.get().onButtonTwoClick(myObjects.get(getAdapterPosition()));
}
}
public interface IObjectClickListener{
void onButtonOneClick(MyObject myObject);
void onButtonTwoClick(MyObject myObject);
}
}
I tried to add new message item who arrived from push notification to list.
I tried to achieve this by live data. I used databinding in recyclerview and in main activity.
The func onChanged is not called when item is added to live data list in MsgViewModel class.
what I doing wrong?
public class MyFirebaseMessagingService extends FirebaseMessagingService {
......
private void showNotification(Map<String, String> data) {
id = data.get("id");
phone = data.get("phone");
locations = data.get("locations");
textMessage = data.get("textMessage");
MsgViewModel viewModel = new MsgViewModel(getApplication());
viewModel.addMessage(new Message(id, phone, locations, textMessage));
}
public class MsgViewModel extends AndroidViewModel {
private MutableLiveData<ArrayList<Message>> messageArrayList;
public MsgViewModel(#NonNull Application application) {
super(application);
messageArrayList = new MutableLiveData<>();
}
public void addMessage(Message message){
List<Message> messages = messageArrayList.getValue();
ArrayList<Message> cloneMessageList;
if(messages == null){
cloneMessageList = new ArrayList<>();
}else {
cloneMessageList = new ArrayList<>(messages.size());
for (int i = 0; i < messages.size(); i++){
cloneMessageList.add(new Message(messages.get(i)));
}
}
cloneMessageList.add(message);
messageArrayList.postValue(cloneMessageList);
}
public MutableLiveData<ArrayList<Message>> getMessageList(){
return messageArrayList;
}
}
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private MsgViewModel msgViewModel;
private MsgListAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.contentMainId.recyclerview.setHasFixedSize(true);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
binding.contentMainId.recyclerview.setLayoutManager(layoutManager);
msgViewModel = new ViewModelProvider(this).get(MsgViewModel.class);
msgViewModel.getMessageList().observe(this, new Observer<ArrayList<Message>>() {
#Override
public void onChanged(ArrayList<Message> list) {
mAdapter = new MsgListAdapter(getApplication(), list);
binding.contentMainId.recyclerview.setAdapter(mAdapter);
// mAdapter.notifyDataSetChanged();
}
});
}
Any help why it is not update the adapter will be appreciated
==========Update=======
onChanged() method not called when have new item who added to the list
The problem here is how you instantiate your viewModels. If I understand correctly, you want them to be the same instance in both the activity and the messaging service.
One is MsgViewModel viewModel = new MsgViewModel(getApplication());
The other one is msgViewModel = new ViewModelProvider(this).get(MsgViewModel.class);
On the second one this stands for the current instance of the activity. And its context is different from the one you get from getApplication().
As far as I know, when you call 'postValue()' or something 'setValue()' method, you should give new object of something like oldMutableList.toList().
In other words, after a list is entered as a parameter in 'postvalue()'method of livedata, even if a new value is added to the list, livedata is not recognized. In order for the observer to recognize, the newly created list object must be entered as a parameter when calling postvalue again.
_liveData.postValue(list.toList()) // "list.toList()" <- this code generate new List object which has another hashcode.
or
_liveData.postValue(list.sortedBy(it.somethingField))
sorry about this kotlin code, not java.
// this original in your code
messageArrayList.postValue(cloneMessageList);
// change to these codes
messageArrayList.postValue(new ArrayList(cloneMessageList));
or
messageArrayList.postValue(cloneMessageList.toList());
I'm trying to save two values from an activity (where the user can put in two different values, one String value and one integer value) in the listview from another activity. In the first activity, it shows a list with a course and the amount of points for that course in one listview, like this:
Course: English
Points: 4
Now, the problem is, everytime I want to put in another value using the add_course_actitivty, it overwrites the previous value. I've looked at different solutions, like with sharedpreferences (Add items to listview from other activity), but this uses only one value and if I try to work with sharedpreferences, it overwrites the other value in the sharedpreferences, but I want users to add multiple courses and corresponding points. Also on restart, it deletes the values in the listview (I read to prevent this you need to store it in sharedpreferences, but this doesn't work the way I need it to be)
KeuzeActivity.class (shows the listview):
public class KeuzeActivity extends AppCompatActivity {
private FloatingActionButton fab_add;
private String student_naam;
private ListView keuze_list;
boolean wantDelete;
private ArrayAdapter adapter;
private String vak;
private int ec;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_keuze);
// setting title
student_naam = getIntent().getStringExtra("student");
setTitle("Keuzevakken en projecten van " + student_naam);
//initialzing elements
fab_add = (FloatingActionButton)findViewById(R.id.fab_add);
keuze_list = (ListView) findViewById(R.id.keuze_list);
//initializing list
final ArrayList<Course> courseItems = new ArrayList<Course>();
adapter = new ArrayAdapter<Course>(this, android.R.layout.simple_list_item_1, courseItems);
keuze_list.setAdapter(adapter);
// checks if intent has required values, put it in listview
if (getIntent().hasExtra("vak") && getIntent().hasExtra("ec")) {
vak = getIntent().getStringExtra("vak");
ec = getIntent().getIntExtra("ec", ec);
courseItems.add(new Course(vak, ec));
adapter.notifyDataSetChanged();
}
// make fab go to other activity
fab_add.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
startActivity(new Intent(KeuzeActivity.this, add_course_activity.class));
}
});
// long press deletes item
keuze_list.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
#Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
showDeleteDialog();
if (wantDelete) {
courseItems.remove(position);
adapter.notifyDataSetChanged();
}
return true;
}
});
}
private void showDeleteDialog() {
AlertDialog.Builder infobuilder = new AlertDialog.Builder(this);
infobuilder.setCancelable(false);
infobuilder.setTitle("Vak/project verwijderen");
infobuilder.setMessage("Weet je zeker dat je het vak of project wilt verwijderen?");
final TextView text = new TextView(this);
// action when pressed OK
infobuilder.setPositiveButton("Ja", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
wantDelete = true;
dialog.cancel();
}
});
infobuilder.setNegativeButton("Nee", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
wantDelete = false;
dialog.cancel();
}
});
infobuilder.show();
}
}
add_course_activity.class (let's users input course and points)
public class add_course_activity extends AppCompatActivity {
private EditText course_edit;
private EditText ec_edit;
private Button save_btn;
private String student_name;
private int ec;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_course);
setTitle("Voeg vak of project toe");
final Context context = getApplicationContext();
// initializing elements
course_edit = (EditText) findViewById(R.id.edit_vak);
ec_edit = (EditText) findViewById(R.id.edit_ec);
save_btn = (Button) findViewById(R.id.save_button);
// action on savebutton
save_btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (course_edit.getText().toString().trim().length() > 0 && ec_edit.getText().toString().trim().length() > 0 ) {
ec = Integer.parseInt(ec_edit.getText().toString());
Intent goBack = new Intent(add_course_activity.this, KeuzeActivity.class);
goBack.putExtra("vak", course_edit.getText().toString());
goBack.putExtra("ec", ec);
goBack.putExtra("student", PreferenceManager.getDefaultSharedPreferences(context).getString("student_name", student_name));
startActivity(goBack);
}
else {
Toast.makeText(context, "Voer juiste informatie in!", Toast.LENGTH_SHORT).show();
}
}
});
}
}
Course.java class (getters and setters + with toString method)
public class Course {
private String vak;
private int ec;
public Course(String vak, int ec) {
this.vak = vak;
this.ec = ec;
}
public String getVak() {
return vak;
}
public void setVak(String vak) {
this.vak = vak;
}
public int getEc() {
return ec;
}
public void setEc(int ec) {
this.ec = ec;
}
#Override
public String toString() {
return ("Vak: " + vak + "\n" + "Punten: " + ec);
}
}
Note that my code isn't clean or done, but to get further I need to fix this problem.
You have several way to do it. As other replies have suggested you can use an SQLLite database and add data to a course table and retrieve data from it.
If you find Db approach to complicated/heavy
You could also use SharedPreferences what you need to do is figure a way to store a string that represent a list of course. It is not the best way to approach it but it will work.
Lets say you choose to serialize your Course object with "vac-ec"
Then you just store a serialized list of course. Example "vac1-ec1,vac2-ec2"
When you need to add a course you juste grab the previous string split it to list, append the new course to the list and re-serialize the list to a string to encode it.
Other solution could be to use Realm.
You should used SQLiteDatabase and create a table with valid attributes and insert your new values into them
Okay, now things are clearer. As answered by #Dwijraj, when storing what potentially will be a large set of data, for maximum control it is best to use SQLite.
You can read more about the different Saving Data methods here:
https://developer.android.com/training/basics/data-storage/index.html
SharedPreferences are best used to store small amounts of information, like storing the settings of an application. [Mute] for example. Or a highscore in case of a game.
A Database is a better option when it comes to storing large pieces of data that you will potentially manipulate.
Your data structure can be something like this, Courses table containing Unique_ID , Course Name, Course Level, Course summary.
A table for English for example which will contain
Exams, Scores, Duration.
There are a lot of things you can do.
Try by storing the records in SQLite, and get it when you want to show.
By this, You can have a track of all added items. And you can show the items you want.
I am writing a small google talk client for android and I am having trouble refreshing my ListView correcty.
This list contains the contact list and is showing the name and the presence of the contact. My listener works fine and I can see the presence changes of each contact in the log cat window, but my ListView is not refreshing... here is some code:
package de.marc.messenger;
// ofc here are the imports
public class RosterActivity extends Activity {
private Roster _roster;
private XMPPConnection _connection;
private List<HashMap<String, String>> _buddies;
private BuddyAdapter _adapter;
private ListView _list;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.roster);
_buddies = new ArrayList<HashMap<String, String>>();
_connection = LoginActivity.CONNECTION;
makePauseForRoster();
_roster = _connection.getRoster();
addRosterListener();
fillBuddyList();
sortBuddyList();
initializeListView();
}
/**
* Lets the thread sleep for a second to ensure that the presence of every
* user will be available
*/
private void makePauseForRoster() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* Adds a listener to the roster, primarily for changes of presence
*/
private void addRosterListener() {
_roster.addRosterListener(new RosterListener() {
public void presenceChanged(Presence presence) {
String user = presence.getFrom().split("/")[0];
HashMap<String, String> buddy = findBuddyInRoster(user);
String p = getPresenceString(user);
buddy.put("presence", p);
System.out.println(buddy.values().toString()); // this works
_adapter.notifyDataSetChanged(); // this doesn't
_list.invalidate(); // this neither
}
});
}
/**
* Fills the list view with the roster entries
*/
private void initializeListView() {
_adapter = new BuddyAdapter(this, R.layout.roster_item,
_buddies);
_list = (ListView) findViewById(R.id.list_roster);
_list.setAdapter(_adapter);
}
/**
* Fills the buddy list with relevant data from a RosterEntry. Relevant data
* is the users' name, email and presence
*/
private void fillBuddyList() {
// this just fills my list of hashmaps (_buddies)
}
/**
* Get a predefined String depending on the presence of a user
*/
private String getPresenceString(String user) {
// something like "available: away ()" -> "away"
}
/**
* Sorts the buddy list. Only criterion is the presence of the user, because
* we have linear algorithms for this kind of problem.
*/
private void sortBuddyList() {
// move all offline contacts to the end
// move all online contacts to the beginning
// all other kind of contacts will stay in the middle
}
/**
* Finds a specific buddy object for a user via his hashed email
*/
private HashMap<String, String> findBuddyInRoster(String user) {
for (HashMap<String, String> buddy : _buddies) {
if (user.equals(buddy.get("user"))) {
return buddy;
}
}
return null;
}
}
This works fine, everything is shown correctly.. only trouble seems to be in the addRosterListener() method, where the onPresenceChanged() is implemented..
Here is my adapter:
package de.marc.messenger;
// as well some imports
public class BuddyAdapter extends ArrayAdapter<HashMap<String, String>> {
private Context _context;
private List<HashMap<String, String>> _map;
private LayoutInflater _inflater;
public BuddyAdapter(Context context, int id, List<HashMap<String, String>> map) {
super(context, id, map);
_context = context;
_map = map;
_inflater = (LayoutInflater) _context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// find view of a single row in a listview
View row = convertView;
if (convertView == null) {
row = _inflater.inflate(R.layout.roster_item, null);
}
// get data for a specific row
String name = _map.get(position).get("name");
String user = _map.get(position).get("user");
String presence = _map.get(position).get("presence");
// extract views from the row view
TextView nameText = (TextView) row.findViewById(R.id.text_name);
TextView userText = (TextView) row.findViewById(R.id.text_id);
ImageView presenceImg = (ImageView) row
.findViewById(R.id.image_presence);
// set data in extracted views
nameText.setText(name);
userText.setText(user);
int resource = 0;
// something is done with this variable
presenceImg.setImageResource(resource);
return row;
}
}
Is there anything I am missing?
Edit: I changed my onPresenceChanged method like this:
public void presenceChanged(Presence presence) {
String user = presence.getFrom().split("/")[0];
HashMap<String, String> buddy = findBuddyInRoster(user);
_adapter.remove(buddy);
String p = getPresenceString(user);
buddy.put("presence", p);
_adapter.add(buddy);
_adapter.notifyDataSetChanged();
}
It works to some extend: After swiping a bit on the screen, the contact that changed his presence is now out of the list :/
When an ArrayAdapter is constructed, it holds the reference for the List that was passed in. If you were to pass in a List that was a member of an Activity, and change that Activity member later, the ArrayAdapter is still holding a reference to the original List . The Adapter does not know you changed the List int he Activity.
It looks to me like that's what you're doing, so you need to recreate the adapter with the new list data.
Or you can use the ArrayAdapter methods to modify the underlying List (add, insert, remove, clear, etc.) Then notifyDataSetChanged wil work.