RecyclerView don't show inserted SQLite data instantly - android

When the user from a FragmentDialog type his data into the edittexts and press the save button, the data should instantly show in the recyclerView, but that dosn't happend. To get/show the latest data, you have to restart the app
I have used a temporary solution where I pass data from the FragmentDialog to the mainActivity via a interface and then I directly pass that data into the arraylist for the recyclerView. This work, but dosn't look like a propor solution. I wish a "correct" way to do it
I have also tried to set adapter.notifyDataSetChanged(); different places without any succses
DialogFragment class where the user type his data and is then insertet to SQLite database
public class DialogAdd extends DialogFragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View rootView = inflater.inflate(R.layout.add_productdialog,container, false);
name = (EditText) rootView.findViewById(R.id.dialog_productname);
quantity = (EditText) rootView.findViewById(R.id.dialog_qantity);
location = (EditText) rootView.findViewById(R.id.dialog_location);
normalPrice = (EditText) rootView.findViewById(R.id.dialog_normalPrice);
offerPrice = (EditText) rootView.findViewById(R.id.dialog_offerPrice);
okButton = (Button) rootView.findViewById(R.id.dialog_okButton);
okButton.getBackground().setColorFilter(Color.parseColor("#2fbd4b"), PorterDuff.Mode.MULTIPLY);
okButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (name.getText().toString().isEmpty()) {
Toast.makeText(context, "You must add a name", Toast.LENGTH_LONG).show();
} else {
DialogAddListener addListener = (DialogAddListener) getActivity();
dbHelper.insertData(name.getText().toString(), quantity.getText().toString(), location.getText().toString(), normalPrice.getText().toString(), offerPrice.getText().toString());
addListener.getDialogData(name.getText().toString(), quantity.getText().toString(), location.getText().toString(), normalPrice.getText().toString(), offerPrice.getText().toString());
getDialog().dismiss();
}
}
});
return rootView;
}
The mainActivity class that instantiate the recyclerview,sqlite and adapters.
public class MainActivity extends AppCompatActivity implements DialogAdd.DialogAddListener{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.shoppinglist_mainactivity);
databaseHelper = new DatabaseHelper(this);
addbutton = (ImageButton) findViewById(R.id.addbtn);
addbutton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dialogAdd = new DialogAdd();
dialogAdd.show(getSupportFragmentManager(), "addDialog");
}
});
//RecyclerView
recyclerView = (RecyclerView)findViewById(R.id.rv_shoppinglist);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(App.getAppContex());
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(linearLayoutManager);
initializeData();
adapter = new ShoplistAdapter(shopListItems);
recyclerView.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
private void initializeData(){
shopListItems = new ArrayList<>();
resultset = databaseHelper.getAllData();
if (resultset.moveToFirst()){
while(!resultset.isAfterLast()){
shopListItems.add(new ShopListItem(resultset.getString(1), resultset.getString(2), resultset.getString(3), resultset.getString(4), resultset.getString(5)));
resultset.moveToNext();
totalPrice.setText("Total cost: $" + "26");
totalItems.setText("Total items: " + resultset.getCount());
}
}
resultset.close();
}
//This is only used to show the data instantly
#Override
public void getDialogData(String name, String qty, String location, String price1, String price2) {
shopListItems.add(new ShopListItem(name, qty, location, price1, price2));
}
}

You should use a CursorLoader which automatically listens for changes in Uri. And notify changes via contentResolver.notifyChanged(uriOfInsertedData). The beast way is to use a ContentProvider to make all thins thing work appropriately.
Or for an easier way, register an Observer in a singleton for your database and notify it upon changing. In general this will do the same and you will not have any relations between components. It takes some code to post so I hope you will figure it out.
Update 2016-03-09, based on your comment
If you implemented example 1, it doesn't tell you how to monitor for changes. There is no existing mechanism for this case.
The best approach to implement this mechanism is to use an Observer pattern.
I would go with a singleton that hosts Observers.
You can extend Observable (which already has logic for storing a list of Observers, notifying and removing them) and make a singleton of it.
Register Observer by calling addObserver() wherever you need to listen to (don't forget to call deleteObserver() to avoid Activity leaks).
Now, whenever you call insert or modify a database in any way, make sure to call notifyObservers("tablename") of Observable singleton.
This way all observers will get notified the table was modified (passing a table name is an example, you can use any Object there to inform your components about the change)
Then in update() of your Observer, check if the table you wish to monitor is updated, if so, reload the data as you normally would do.

Related

Send live data from ViewModel to Adapter of ViewPager2

I have a sharedViewModel with a live data variable that sends data to my fragments, everything goes well and I can capture the variables in the fragments.
However, as recommended I get the sharedViewModel inside onViewCreated, as follows below:
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
DataInitialViewModel initial = new ViewModelProvider(requireActivity()).get(DataInitialViewModel .class);
initial .getStatusUser().observe(getViewLifecycleOwner(), new Observer<String>() {
#Override
public void onChanged(String s) {
if(ConstsSubscribe.USER_STANDARD.equals(s)){
if(adapter != null)
isAssinante = true;
Toast.makeText(getContext(), "test", Toast.LENGTH_SHORT).show(); // Never enter here
}else{
if(adapter != null)
isAssinante = false;
Toast.makeText(getContext(), "comaefeq", Toast.LENGTH_SHORT).show(); // Never enter too...
}
}
});
}
The problem is that I fill an adapter in my onCreateView, however, that adapter is filled using Firebase data (which takes some time).
So, even with the observer, my variable can never capture when the adapter is different from null, keeping it always null, I can never pass the state of my variable to my adapter. Is there any way to capture my live data inside my adapter?
adapter = new DieAdapter(root.getContext(), dieArrayListAdp, manager, isAssinante); // var isAssinante is never updated... adapter is send to ViewPager2 to setAdapter command after.
I read the live data on onCreateView, works fine, is the unique solution for my problem, even tough not be the greater.

Passing data from one activity to interactor of another activity

I'm developing a chat application. There are chatrooms and inside these rooms there are messages. When a user clicks on the chatroom I want to go to another activity where messages are displayed.
In my adapter class, I have this onclick() method written in onBindViewHolder where I would normally make an intent along with the data I need. Something like this:
#Override
public void onBindViewHolder(#NonNull ChatRoomAdapter.ChatRoomViewHolder holder, final int position) {
holder.mRoomTitle.setText(mChatRooms.get(position).getTitle());
holder.mRoomDescription.setText(mChatRooms.get(position).getDescription());
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(this, NextActivity.java);
intent.putExtra("test", mChatRooms.get(position).getTitle());
}
});
}
But I'm trying the MVP architecture design and I want to pass roomTitle to the Interactor/presenter class of my next activity. How can I achieve this?
In RecyclerView adapter you need to pass a onItemClickListener in the adapter.
Refer to the Google's MVP sample - > https://github.com/googlesamples/android-architecture/tree/todo-mvp/
Especially refer the TaskItemListener in TaskFragment. They are doing the same thing what you are trying to achieve. In this they open Task details (new activity) from List of tasks(recyclervView).
/**
* Listener for clicks on tasks in the ListView.
*/
TaskItemListener mItemListener = new TaskItemListener() {
#Override
public void onTaskClick(Task clickedTask) {
mPresenter.openTaskDetails(clickedTask);
}
#Override
public void onCompleteTaskClick(Task completedTask) {
mPresenter.completeTask(completedTask);
}
#Override
public void onActivateTaskClick(Task activatedTask) {
mPresenter.activateTask(activatedTask);
}
};
And then pass it to adapter of Recycler view
mListAdapter = new TasksAdapter(new ArrayList<Task>(0), mItemListener);
And on item click
rowView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mItemListener.onTaskClick(task);
}
});
Follow this article to know more about MVP.
You can do one thing that creates a method in the next activity's presenter
setRoomTitle(String roomTitle);
Whenever, you click and send intent and get in next Activity call that
mPresenter.setRoomTitle(roomTitle);
Is it make sense? So, you can sent your title or other data in next activity's presenter.
Let me know if you have more query then.
Thanks.
Happy coding :)
The adapter is only responsible for binding the view and the data together. Your business logic should go into the controller class which is your Activity or Fragment containing the RecyclerView. This way you can reuse it for any other Activity, and it also makes debugging/maintaining a lot easier since you know that your logic code is in one place.
But how do you link both together? It's simply done by implementing a callback interface and passing it to your adapter. A callback interface could be something like this:
interface OnClickCallback{
void onClick(String title);
}
Just add a member variable to your adapter class called mCallback for example and affect a reference to it through the adapter constructor or through a setter method.
You can either make your Activity implement this interface and pass itself as the reference or you can instantiate it in an object and pass it instead.
Then just write this:
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mCallback.onClick(mChatRooms.get(position).getTitle());
}
});
The onClick method should create an intent to your new Activity with an extra containing the title. In your new Activity's onCreate method, you can retrieve the title value by using:
String title = getIntent().getStringExtra(YOUR_TITLE_KEY_HERE);

How to check for string changes after a user presses a Button?

This question is hard to word, but explaining is easy.
I have an Activity, where a user can modify their origin and personal description.
Once all changes are made, they press the "done" Button.
Now I want to know the most optimal way of checking if the strings they entered is novel and not equal to the previous description and origin.
Because... what if they only change description, and not origin, vice versa, or they change both?
Not sure the most optimal way of doing it.
Here is my current Activity.
public class EditProfile extends AppCompatActivity
{
private TextView originTV;
private EditText descriptionET;
private ImageView cameraIV, mainIV;
private Button doneButton;
private static final String EDIT_PROFILE = "EDIT_PROFILE";
private UpdateUserString updateUserString;
private int PLACE_AUTOCOMPLETE_REQUEST_CODE = 20;
private User localActivityUser;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_16e_profile_edit);
bindActivity();
}
private void bindActivity()
{
localActivityUser = Global_Class.getInstance().getValue().user;
originTV = (TextView) findViewById(R.id.editProfile_originTV);
descriptionET = (EditText) findViewById(R.id.editProfile_descriptionET);
cameraIV = (ImageView) findViewById(R.id.editProfile_cameraIV);
mainIV = (ImageView) findViewById(R.id.editProfile_imageIV);
doneButton = (Button) findViewById(R.id.editProfile_doneButton);
doneButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if(!localActivityUser.getDescription().equals(descriptionET.getText().toString()))
{
//Call api to update my backend.
UpdateUserStringAsyncTask updateUserStringAsyncTask = new UpdateUserStringAsyncTask("description",descriptionET.getText().toString());
updateUserStringAsyncTask.execute();
}
else if(!localActivityUser.getOrigin().equals(originTV.getText()))
{
//Call api to update my backend
UpdateUserStringAsyncTask updateUserStringAsyncTask = new UpdateUserStringAsyncTask("origin",originTV.getText().toString());
updateUserStringAsyncTask.execute();
}
}
});
}
The most optimal way would be not to care about checking every combination of valid changes, and send the whole user object to the API. Multiple network requests is bad on your device's resources. Let the database handle updating the data, even if it is the same. That isn't a problem for your app.
doneButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// These two could be in a TextWatcher instead
localActivityUser.setDescription(
descriptionET.getText().toString());
localActivityUser.setOrigin(
originTV.getText().toString());
UpdateUserStringAsyncTask task = new UpdateUserStringAsyncTask(localActivityUser);
task.execute();
}
});
You could also look into Android Data Binding library so that you don't need to worry about maintaining the state of the User class yourself.

How can I populate fragments with data that aren't available until after the fragment's creation?

I have an activity that grabs data via WebService, from there it creates elements to display the data. Some data is grouped so my solution was to display the grouped data in their own fragments below the main layout, allowing the user to swipe across the groups, probably with a tabs at the top to show the group name.
The problem I came across was that the fragments in the activity are created before that web call takes place, making them empty or using old data. I then created a sharedpreferences listener and placed the fragments layout creation method within it. The main method grabs the data, writes to sharedpreferences the fragment detects the change and creates it's layout, Or so I thought.
Some groups are the same between items, so moving from one to the other won't trigger that onchange event thus not triggering the layout creation method. I then decided to do the following to always trigger the onchange event after the sharedpreferences are written
final Boolean updated = settings.getBoolean("UPDATED_1", false);
SharedPreferences.Editor editor = settings.edit();
editor.putBoolean("UPDATED_" + pageNum, !updated);
I just don't think that's the best solution, it also has it's problems and isn't triggering every time (Which I have yet to troubleshoot)
What's a better solution for all this? I also have a memory leak I haven't diagnosed yet to make things even more of a headache.
I've just thought of moving my data grabbing method to before the ViewPager initialization but I'm not yet sure if this will solve my problem.
I would not recommend waiting until you get the data to show the view as it will affect the User Experience and look sluggish.
Instead, you could implement an AsyncTaskLoader in your fragment so you can inform the Fragment's View with a BroadcastReceiver once you get the data from your server. In the meantime, just show a spinner until the data are retrieved, then you hide it and update your list with a adapter.notifyDataSetChanged();.
Here is an example of a AsyncTaskLoader (In my case it's a database query instead of a server call like you):
public class GenericLoader<T extends Comparable<T>> extends AsyncTaskLoader<ArrayList<T>> {
private Class clazz;
public GenericLoader(Context context, Class<T> clazz) {
super(context);
this.clazz = clazz;
}
#Override
public ArrayList<T> loadInBackground() {
ArrayList<T> data = new ArrayList<>();
data.addAll(GenericDAO.getInstance(clazz).queryForAll());
Collections.sort(data);
return data;
}
}
Then in your Fragment:
public class FragmentMobileData extends Fragment implements ListAdapter.OnItemClickListener, LoaderManager.LoaderCallbacks<ArrayList<EntityCategories.EntityCategory>> {
public static String TAG = "FragmentMobileData";
private ImageListAdapter adapter;
private ArrayList<EntityList> mCategories = new ArrayList<>();
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Bundle bundle = intent.getExtras();
String result = bundle.getString(DatabaseService.RESULT);
if (DatabaseService.NO_CONNECTION.equals(result)) {
Utils.showToastMessage(getActivity(), "No internet connexion", true);
} else if (DatabaseService.RESULT_TIMEOUT.equals(result)) {
Utils.showToastMessage(getActivity(), "Bad connection. Retry", true);
}
getActivity().getSupportLoaderManager().initLoader(1, null, FragmentMobileData.this).forceLoad();
}
};
#Bind(R.id.progressBarEcard)
ProgressBar spinner;
#Bind(R.id.list)
RecyclerView list;
public FragmentMobileData() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_mobile_plan, container, false);
ButterKnife.bind(this, view);
((AppCompatActivity) getActivity()).getSupportActionBar().setTitle("Mobile");
list.setLayoutManager(new LinearLayoutManager(context));
list.addItemDecoration(new DividerItemDecoration(context, R.drawable.divider));
adapter = new ImageListAdapter(mCategories, this);
list.setAdapter(adapter);
Intent intent = new Intent(context, DatabaseService.class);
intent.setAction(DatabaseService.UPDATE_DATA);
getActivity().startService(intent);
return view;
}
#Override
public void onPause() {
super.onPause();
getActivity().unregisterReceiver(mReceiver);
}
#Override
public void onResume() {
super.onResume();
getActivity().registerReceiver(mReceiver, new IntentFilter(DatabaseService.UPDATE_DATA));
}
#Override
public Loader<ArrayList<EntityCategories.EntityCategory>> onCreateLoader(int id, Bundle args) {
return new GenericLoader(context, EntityCategories.EntityCategory.class);
}
#Override
public void onLoadFinished(Loader<ArrayList<EntityCategories.EntityCategory>> loader, ArrayList<EntityCategories.EntityCategory> data) {
if (mCategories.size() != data.size()) {
mCategories.clear();
mCategories.addAll(data);
adapter.notifyDataSetChanged();
Intent intent = new Intent(context, DownloadFilesService.class);
context.startService(intent);
}
spinner.setVisibility(View.GONE);
}
#Override
public void onLoaderReset(Loader<ArrayList<EntityCategories.EntityCategory>> loader) {
mCategories.clear();
adapter.notifyDataSetChanged();
}
//...
}
Maybe I misunderstood something. But in your case I think there is pretty good alternative to create, for example, your fragment which will display some group of data, then in it's creation stage show progress bar in ui, and meantime do request to the data in background. Then handle result data and show it, and hide progress bar.
This can be achieved with implementing MVP pattern to provide flexibility of code and easy testing. Also you can use rxJava and Retrofit to handle requests in a convenient way. More information about MVP and samples you can find here.
If you don't want to provide this way for some reason. For example, you have undetermined number of groups, which you will receive in future somehow and you want to dynamically build your fragments base on data which you receive, then I suggest you can organize presentation layer in your activity. In this layer your will receive data then pass it to special handler, which will divide it to groups and base on them will ask activity to create fragment. In constructor you will send already received data (so it is need to implement Parcelable interface).

How to get listener of a view

I write service that interacts with other apps. It registers listeners on views (buttons, textviews,...), that already have listeners. I need to replace them with my own listeners (works), do some stuff and then unregister my listeners and restore the old ones.
An App with a button with an onClickListener is running
My service registers an onClickListener inside the UI-Thread + do something
My service restores the old listener
It would be easy, if there was a view.getOnClickListener -method. Then I could save the old ones and replace the new listeners when I'm done.
Is there any way to get listeners from a view or have more that one listener of the same type bound to one view?
Button btn = (Button) findViewById(R.id.btn1);
btn.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
//do something
}
});
// I need to do, but found no solution for that.
View.OnClickListener oldListener = btn.getOnClickListener();
If I register the new listeners to a view, the old ones are overridden, right? It would be also okay if both listeners ("new" and "old") exist at the same time. Only the old ones must not be gone.
edit: Unfortunately I have no possibility to save the listener on assignment. I need to get it backwards from the view component.
Thanks
Thanks to mihail's hint (thanks for that :)) )with the hidden API, I've found a solution to get a listener back after assignment:
The android.view.View class has a nested class static class ListenerInfo that stores all listeners on a View (API 14+). In older versions the listeners are private fields in the android.view.View.
The field can be accessed with reflection. In my case (API 14+),
// get the nested class `android.view.View$ListenerInfo`
Field listenerInfoField = null;
listenerInfoField = Class.forName("android.view.View").getDeclaredField("mListenerInfo");
if (listenerInfoField != null) {
listenerInfoField.setAccessible(true);
}
Object myLiObject = null;
myLiObject = listenerInfoField.get(myViewObj);
// get the field mOnClickListener, that holds the listener and cast it to a listener
Field listenerField = null;
listenerField = Class.forName("android.view.View$ListenerInfo").getDeclaredField("mOnClickListener")
if (listenerField != null && myLiObject != null) {
View.OnClickListener myListener = (View.OnClickListener) listenerField.get(myLiObject);
}
After that code (I missed a lot of try-catch-blocks), the myListener object holds the instance of the onClickListener, that has been anonymously declared to the view before. It also works with any other listener, just replace the "mOnClickListener parameter" with the one you need in the reflection and cast it correctly.
Note that code changes in upcoming versions can make that not working anymore.
Found the final tutorial here: http://andwise.net/?p=161
create classes that implements OnClickListener
public static class MyClickListener1 implements OnClickListener{
Activity mActivity;
MyClickListener1(Acivity activity){
mActivity=activity;
}
#Override
public void onClick(View v) {
//do something
}
}
public static class MyClickListener2 implements OnClickListener{
#Override
public void onClick(View v) {
//do something
}
}
and in your code you can easily use them:
btn.setOnClickListener(new MyClickListener1(this));
btn.setOnClickListener(new MyClickListener2());
or you can create instances and reuse them:
OnClickListener listener1 = new MyClickListener1(this);
OnClickListener listener2 = new MyClickListener2();
btn.setOnClickListener(listener1);
btn.setOnClickListener(listener2);
you can also define a constructor to pass whatever you need in these classes. I usually pass the activity like in MyClickListener1
EDIT: If you want to have the listener like object in the button, you can use the tag.
btn.setTag(listener1);
btn.setOnClickListener(listener1);
and then to get it use
OnClickListener old_listener = (OnClickListenr)btn.getTag();
Make two instances of OnCLickListener and assign first or second to button:
Button b = (Button) findViewById(R.id.Button1);
OnClickListener listener_new = new OnClickListener() {
#Override
public void onClick(View v) {
Log.d("APP", "NEW CLICK LISTENER ACTIVE");
}
};
OnClickListener listener_old = new OnClickListener() {
#Override
public void onClick(View v) {
Log.d("APP", "OLD CLICK LISTENER ACTIVE");
}
};
//setting listener
b.setOnClickListener(listener_old);
b.callOnClick();
//changing listener
b.setOnClickListener(listener_new);
b.callOnClick();
//return your old listener!
b.setOnClickListener(listener_old);
b.callOnClick();
ADDED:
OnClickListener is protected field of Button class, inherited from View class. Name of the field "mOnClickListener". I can't get it even through reflection.
void getListener(Button b) {
java.lang.reflect.Field field = getClass().getSuperclass().getSuperclass().getDeclaredField("mOnClickListener");
}
So You can't get existing listener of the Button if You don't have access to code where it created.
But if You have access to objects of Activity (and we know You have because setting new listener to button), You could add your button with your listener on that activity. Make existing button invisible. And than rollback when necessary.
public abstract class ReflectionUtils {
private static final String listenerInfoFieldName = "mListenerInfo";
private static final String onCLickListenerFieldName = "mOnClickListener";
public static OnClickListener getOnClickListener(View view){
Object listenerInfo = ReflectionUtils.getValueFromObject(view, listenerInfoFieldName, Object.class);
return ReflectionUtils.getValueFromObject(listenerInfo, onCLickListenerFieldName, View.OnClickListener.class);
}
public static <T> T getValueFromObject(Object object, String fieldName, Class<T> returnClazz){
return getValueFromObject(object, object.getClass(), fieldName, returnClazz);
}
private static <T> T getValueFromObject(Object object, Class<?> declaredFieldClass, String fieldName, Class<T> returnClazz){
try {
Field field = declaredFieldClass.getDeclaredField(fieldName);
field.setAccessible(true);
Object value = field.get(object);
return returnClazz.cast(value);
} catch (NoSuchFieldException e) {
Class<?> superClass = declaredFieldClass.getSuperclass();
if(superClass != null){
return getValueFromObject(object, superClass, fieldName, returnClazz);
}
} catch (IllegalAccessException e) {
}
return null;
}
}
Calling OnClickListener onClickListener = ReflectionUtils.getOnClickListener(myView); on myView will give you the myView's listener that you are looking for.

Categories

Resources