I apologize if this is a duplicate.
I'm currently working on a ToDo app and I'm using the Room database library to display a list of items using a RecyclerView. From the main activity, I am able to access the information from the database using the adapter. No problem here.
The thing is that when I press on one of the items, I want to open a DetailActivity where the user can modify the ToDo. Currently I'm getting the desired information using putExtra and hasExtra:
MainActivity
private void initializeAdapterForRecyclerView(){
mAdapter = new ToDoAdapter(new ToDoAdapter.ToDoClickListener() {
#Override
public void onToDoClick(int clickedItemIndex) {
Intent intent = new Intent(ToDoActivity.this, EditorActivity.class);
Log.d(LOG_TAG, "This is " + mAdapter.getToDoPosition(clickedItemIndex).getTitle());
intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(clickedItemIndex));
intent.putExtra("existingTitle", mAdapter.getToDoPosition(clickedItemIndex).getTitle());
intent.putExtra("modifiedTitle", mAdapter.getToDoPosition(clickedItemIndex).getDisplayTitle());
intent.putExtra("existingDescription", mAdapter.getToDoPosition(clickedItemIndex).getDescription());
startActivity(intent);
}
});
mToDoList.setAdapter(mAdapter);
}
DetailActivity
private void getExistingToDoContent() {
Intent intent = getIntent();
if (intent.hasExtra("existingTitle")) {
String displayTitle = intent.getStringExtra("modifiedTitle");
Log.d(LOG_TAG, "Getting the title: " + displayTitle);
mTitleEditText.setText(displayTitle);
String existingDescription = intent.getStringExtra("existingDescription");
Log.d(LOG_TAG, "Getting the description: " + existingDescription);
mDescriptionEditText.setText(existingDescription);
}
}
The issue comes when I want to delete the item from the DetailActivity as I want to do this with the the ViewModel class.
ToDoViewModel
public void delete(ToDo toDo){
mRepository.delete(toDo);
}
ToDoRepository
public void delete(ToDo todo){
new deleteAsyncTask(mToDoDao).execute(todo);
}
private static class deleteAsyncTask extends AsyncTask<ToDo, Void, Void> {
private ToDoDao mAsyncToDoDao;
deleteAsyncTask(ToDoDao dao){
mAsyncToDoDao = dao;
}
#Override
protected Void doInBackground(ToDo... toDos) {
mAsyncToDoDao.deleteToDo(toDos[0]);
return null;
}
}
}
So my question is, is it possible to access the database entry of the clicked item from the DetailActivity to populate the fields and delete / update the entry using the methods from the ViewModel activity? (other than using putExtra / hasExtra)
Thank you
Related
I'm creating an Android app with an activity with a bottom navigation control that lets the user navigate between different fragments. In these fragments i have lists of data coming from a firebase backend that i show with a RecyclerView.
The problem is that every time i navigate between these fragments all the data is downloaded again, while i would want to use cached data and just listen for changes.
What i have done so far is to use ViewModel and LiveData and they work fine. Moreover if i disconnect the phone from the Internet the data is showed (and of course is not downloaded), even if i navigate between the fragments.
In the fragment that shows the data i have:
LiveData<List<UncompletedTask>> taskLiveData = viewModel.getTaskLiveData();
taskLiveData.observe(this, new Observer<List<UncompletedTask>>() {
#Override
public void onChanged(List<UncompletedTask> uncompletedTasks) {
myAdapter.submitList(uncompletedTasks);
listener.onTodoListElementsLoaded(uncompletedTasks.size());
}
});
In the viewmodel i have:
private TodoTaskRepository repository;
#NonNull
public LiveData<List<UncompletedTask>> getTaskLiveData() {
return repository.getTaskLiveData();
}
In the TodoTaskRepository i initialize FirebaseQueryLiveData in the contructor and return it in getTaskLiveData().
Finally FirebaseQueryLiveData is like this:
public class FirebaseQueryLiveData extends LiveData<DataSnapshot> {
private static final String LOG_TAG = "FirebaseQueryLiveData";
private final Query query;
private final MyValueEventListener listener = new MyValueEventListener();
public FirebaseQueryLiveData(Query query) {
this.query = query;
}
#Override
protected void onActive() {
query.addValueEventListener(listener);
}
#Override
protected void onInactive() {
query.removeEventListener(listener);
}
private class MyValueEventListener implements ValueEventListener {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
setValue(dataSnapshot);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.e(LOG_TAG, "Can't listen to query " + query, databaseError.toException());
}
}
}
How can i download all the data the first time but then just listen for changes and don't download the same data while navigating between fragments if nothing is changed?
If you have enabled disk persistence then data will not be download again unless data has changed
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
When you run your ValueEventListener the first time data is downloaded alright, the second time the same ValueEventListener runs then data is coming from local cache persistent
Moreover if disconnect the phone from the Internet the data is indeed coming from the same local cache.
I want to have the info of a member passed to the second activity.
This is the code in the first activity.
public void onMemberClick(int position) {
Member member = mMember.get(position);
Intent intent = new Intent(getApplicationContext(),MemberInfo.class);
intent.putExtra("MemberID", member.getId());
MemberInfo.open(this, member.getId());
This is the code in the second activity.
public static void open(Activity activity, long memberid) {
Intent intent = new Intent(activity, MemberInfo.class);
intent.putExtra("MemberID", memberid);
activity.startActivity(intent);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memberinfo);
Intent intent = getIntent();
if (intent != null && intent.hasExtra("MemberID")) {
long memberid = intent.getLongExtra("MemberID", -1);
// TODO: get customer details based on customer id
TextView firstname = findViewById(R.id.layout_memberfirstname);
TextView surname = findViewById(R.id.layout_membersurname);
TextView balance = findViewById(R.id.layout_memberbalance);
}
else {
Toast.makeText(
getApplicationContext(),
R.string.empty_not_saved,
Toast.LENGTH_LONG).show();
}
So in the first activity, I got a list with members. I click on a member and I want to have the ID of the member passed through the open method. The ID should be passed to the second activity.
A member has a first name, surname and balance. I want to get those details shown in the Textviews. How can I get those information by using the ID of that member?
Try this.
Java:
#Query("select * from user where id= :id")
User getUserById(Long id);
Kotlin:
#Query("select * from user where id= :id")
fun getUserById(id: Long) : User
Hope this helps
Query to get member list
#Query("SELECT firstname, surname FROM Member WHERE user IN (:users)")
public List<Member> Memberlist(List<String> members);
make query like below into dao interface in room db..
#Query("SELECT * FROM TableName WHERE id=:id")
User getUserData(long id);
create app level activity..
public class AppActivity extends Application {
static AppDatabase db;
#Override
public void onCreate() {
super.onCreate();
db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "database-name").build();
}
public static AppDatabase getDatabase() {
return db;
}
}
this activity define into android manifest file in application tag..
android:name=".db.AppActivity" // this line add into application tag.
after that define db version and other things..
#Database(entities = {MyTable.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract MyTableDao getTableDao();
}
after that in second activity getting member id then perform above query like below..
Member member=AppActivity.getDatabase().getTableDao().getData(memberId);
make sure table has data and member id not null.
after you want show all details without factching data then pass all data into intent and get data using intent.
I had to create a new method in my DAO. This query did the trick: SELECT * FROM member_table WHERE MemberID=:id
This query then should return a object of the class you try to get, in my case:
Member getInfo(long id);
I want to retrieve all the records from table and show it in another activity,but when I do I am getting one record at a time and in reverse order.
I want all the records in single activity
#Override
public void onClick(View v) {
Intent i = new Intent(MainActivity.this, ViewAllActivity.class);//
while (c.moveToNext()) {
i.putExtra("rollno", c.getString(0));
i.putExtra("name", c.getString(1));
i.putExtra("marks", c.getString(2));
startActivity(i);
}
}
});
ViewAllActivity
String rollno=getIntent().getStringExtra("rollno");
String name=getIntent().getStringExtra("name");
String marks=getIntent().getStringExtra("marks");
TextView text;
text=(TextView) findViewById(R.id.text);
String str="Roll no:"+rollno+"\nName:"+name+"\nmarks:"+marks;
text.setText(str);
You have to create a model for doing the same..
example :
ArrayList<myModel> modelList = new ArrayList<>();
while(c.moveToNext()){
MyModel myModel = new MyModel();
myModel.setRollNumber(c.getString(0));
myModel.setName(c.getString(1));
myModel.setMark(c.getString(2));
modelList.add(myModel);
}
}
});
intent.putParcelableArrayListExtra(TAG, modelList);
startActivity(intent);
MyModel
public class MyModel implements Parcelable{
private String rollNumber;
private String name;
private String mark;
public void setRollNumner(String rollNumber){
this.rollNumber = rillNumber;
}
.....
}
the model should implement Parcelable or Serializable
I'l prefer just start your activity in the button click, and retrieve the data in the second activity.
#Override
public void onClick(View v) {
Intent i = new Intent(MainActivity.this, ViewAllActivity.class);
startActivity(i);
}
});
ViewAllActivity
//retrieve data to your cursor
String str = "";
while(c.moveToNext()){
str = str + "Roll no:" + rollno + "\nName:" + name + "\nmarks:" + marks;
text.setText(str);
}
If i were you i just start new intent activity and in some method in the second activity, oncreate or onstart retrieve your data from db.
But if you want to send your data in a bundle to the another one, you have to consider something
1.- you have to create a lis of your objets to send.
2.- to send arraylist or objects with a bundle you have to implement and extend Parcelable interface, otherwise will fail.
3.- if you use to send data vía bundle, is not good idea, could weak your performance, just think if your list are many objects, and you want to send vis bundle, app will bd slow. So think about.
Regards.
I have created a class that holds common variables and functions and is inherited by the activity classes that interface with the different UI pages in my app. I have been passing information between classes and activities using getVariable() and setVariable(input) functions. Suddenly, I can no longer pass information this way (it had been working well until recent edits, and now I can't figure out which change screwed this up). I have used Log outputs to determine that the data is storing properly - with the setVariable(input) functions - but when called later with the getVariable() functions it returns null. Any thoughts?
*Note, I recently started incorporating fragments into my project, extending FragmentActivity instead of Activity on my main class. I don't think this is causing the problem, but could it? If it does, whats the best practice to pass global variable info, and use fragments?
Code samples:
Main Inherited class:
public class MenuBarActivity extends FragmentActivity {
private String keyA;
private String keyB;
private int token;
private String Salt;
private long expires;
public String getKeyB() {
return keyB;
}
public String getKeyA() {
return keyA;
}
public int getTokenID() {
return token;
}
public void setToken(int tkn) {
token = tkn;
}
public void setKeyB(String kyB) {
keyB = kyB;
}
public void setKeyA(String kyA) {
keyA = kyA;
}
//Other common functions
}
LogIn Activity Class (gets log in info from web, stores into global variables):
public class WebContentGet extends MenuBarActivity{
public int tryLogOn(String uEmail, String pw) {
//call to get new keys on start up
JSONObject jObSend = new JSONObject();
try {
jObSend.put("email", uEmail);
jObSend.put("password", pw);
t.start();
t.join();
if(getStatus() == USER_STATUS_SUCCESSFULLOGIN){
String data = getData();
JSONObject jObReturn = new JSONObject(data);
String kyA = jObReturn.getString("keyA");
String kyB = jObReturn.getString("keyB");
int tkn = Integer.parseInt(jObReturn.getString("tokenID"));
String salt = jObReturn.getString("salt");
long exp = Long.parseLong(jObReturn.getString("expiration"));
int uID = Integer.parseInt(jObReturn.getString("userID"));
// Log outputs confirm data being read properly, and reported to setX() functions
setToken(tkn);
setKeyA(kyA);
setKeyB(kyB);
setSalt(salt);
setExpires(exp);
Log.d("WebContentGet tryLogIn","login values stored");
}
return getStatus();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return getStatus();
}
}
Activity Class, checks if keyA/B/etc stored:
public class UserLogIn2 extends MenuBarActivity implements EmailListener {
String emailIn;
String pwIn;
Context context = this;
#Override
public void onEmailLogInClick(String email, String pw) {
Log.d("UserLogin2", "onEmailLogInClick");
emailIn = email;
pwIn = pw;
emailIn = emailIn.trim();
emailIn = emailIn.toUpperCase();
Log.d("prepped email", emailIn);
pwIn = pwIn.trim();
WebContentGet webOb = new WebContentGet();
int webLog = webOb.tryLogOn(emailIn, pwIn);
if (webLog == USER_STATUS_SUCCESSFULLOGIN) {
int tkn = getTokenID();
long exp = getExpires();
String kya = getKeyA();
String kyb = getKeyB();
String slt = getSalt();
Log.d("UserLogIn2 - token", String.valueOf(tkn));
//Log statements confirm that getX() functions returning null
session.storeLoginSession(emailIn, pwIn, thisUser, tkn, exp, kya, kyb, slt);
Intent intent1 = new Intent(context, MainActivitiy.class);
startActivity(intent1);
} else {
showDialog(this, "Log in failure", "Incorrect Password");
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.userlogin2);
}
}
This cannot work, because you have two differend instances of your MenuBarActivity. Also that is not the way to pass data from one activity to another in android.
If you want to use data from one activity in another activity, you have to add them to an intent in the activity which provides the data, and extract them in the other. For more information see here: How do I pass data between Activities in Android application?
If you don't want to start the activity and send the data with the intent, you have to store the data somewhere e.g. SharedPreferences and fetch them again: How to use SharedPreferences in Android to store, fetch and edit values
I have extensive use of ArrayAdapter in my app because most Activities are holding a ListView and I need some custom stuff in them.
I took a look at the test classes in the android developer documentation but wasn't able to find some examples or a proper testclass...
1) Are there any best practices for (unit)-testing ArrayAdapter in Android?
2) May I have chosen the wrong approach (with the adapters) and killed testability this way?
You can write the test extending AndroidTestCase It will looks something like this:
public class ContactsAdapterTest extends AndroidTestCase {
private ContactsAdapter mAdapter;
private Contact mJohn;
private Contact mJane;
public ContactsAdapterTest() {
super();
}
protected void setUp() throws Exception {
super.setUp();
ArrayList<Contact> data = new ArrayList<Contact>();
mJohn = new Contact("John", "+34123456789", "uri");
mJane = new Contact("Jane", "+34111222333", "uri");
data.add(mJohn);
data.add(mJane);
mAdapter = new ContactsAdapter(getContext(), data);
}
public void testGetItem() {
assertEquals("John was expected.", mJohn.getName(),
((Contact) mAdapter.getItem(0)).getName());
}
public void testGetItemId() {
assertEquals("Wrong ID.", 0, mAdapter.getItemId(0));
}
public void testGetCount() {
assertEquals("Contacts amount incorrect.", 2, mAdapter.getCount());
}
// I have 3 views on my adapter, name, number and photo
public void testGetView() {
View view = mAdapter.getView(0, null, null);
TextView name = (TextView) view
.findViewById(R.id.text_contact_name);
TextView number = (TextView) view
.findViewById(R.id.text_contact_number);
ImageView photo = (ImageView) view
.findViewById(R.id.image_contact_photo);
//On this part you will have to test it with your own views/data
assertNotNull("View is null. ", view);
assertNotNull("Name TextView is null. ", name);
assertNotNull("Number TextView is null. ", number);
assertNotNull("Photo ImageView is null. ", photo);
assertEquals("Names doesn't match.", mJohn.getName(), name.getText());
assertEquals("Numbers doesn't match.", mJohn.getNumber(),
number.getText());
}
}
Probably you will have to test getView several times with different arguments, to test all scenarios.