I currently have a List View that takes data from an ArrayList that gets update every certain amount of time in the MainActivity. I want to update the List View every certain amount of time, let say 2 seconds. The problem is that I have this Adapter on a fragment and if I call the notifyDataSetChanged(); from the Adapter, the View only gets update if I switch between the MainActivity and the fragment. I want that this List view refreshes every 2 seconds while I'm having the fragment in View. I have tried to run a TimerTask on the Adapter class, but I get an exception:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the
original thread that created a view hierarchy can touch its views.
I guess you can only update the view from the MainActivity that has the fragment attached.
This how I was running the timer:
public void setTimer(int seconds) {
timer = new Timer();
timer.schedule(new RemindTask(), seconds * 1000);
}
public class RemindTask extends TimerTask {
//Refreshing list view
public void run() {
notifyDataSetChanged();
timer.cancel();
setTimer(2);
}
}
Would it be safe to create an instance of the adapter on the MainActivity and call notifyDataSetChanged() every certain amount of time?
You can Create one broadcast receiver in Fragment. with certain action name.
Lets say "refresh_data".
Then send this broadcast from MainActivity.
Example Code.
public class MyFragment extends Fragment{
private BroadcastReceiver refreshData = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
myAdapter.notifyDataSetChanged();
}
};
#Override
public void onResume() {
super.onResume();
LocalBroadcastManager.getInstance(getContext()).registerReceiver(refreshData,
new IntentFilter("refresh_Data"));
}
#Override
public void onPause() {
LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(refreshData);
super.onPause();
}
}
In Activity call following method whenever your list is updated
Intent intent = new Intent("refresh_data");
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
Related
I am using a Handler to display a timer in RecyclerView list item. When I press back the Activity that hosts the RecyclerView is completely destroyed, the Handler() still running in the background. The handler is created and initiated in ViewHolder. Is there any way to remove the callbacks from handler from ViewHolder itself?
My ViewHolder sample code
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), CustomRunnable.CustomRunnableListener{
private val handler = Handler()
lateinit var customRunnable: CustomRunnable //custom runnable for my timer logic
fun bind(position: Int, listModelClass: ModelClass?){
if(someCondition){
customRunnable = CustomRunnable(handler, this, textView, listModelClass)
handler.postDelayed(customRunnable, 1000)
}
}
override fun onTimerFinish(listModelClass: ModelClass) {
// I get this call back when the timer finishes
handler.removeCallbacks(customRunnable)
}
}
As per my knowledge, there is no method on adapter that is called when RecyclerView is detached from activity.
Try creating a timer object or a list of objects in your BaseActivity or Application Class and after pressing onBack run a method that will stop that timer or timers.
//Declare timer
CountDownTimer cTimer = null;
//start timer function
void startTimer() {
cTimer = new CountDownTimer(30000, 1000) {
public void onTick(long millisUntilFinished) {
}
public void onFinish() {
}
};
cTimer.start();
}
//cancel timer
void cancelTimer() {
if(cTimer!=null)
cTimer.cancel();
}
You can do it onDestory() of activity
handler.removeCallbacksAndMessages(null);
Remove any pending posts of callbacks and sent messages whose obj is token. If token is null, all callbacks and messages will be removed.
if your Activity extended from androidx.activity.ComponentActivity, you can do this easily, just bind the lifecycle event then the super class would done their job as your desired, the sample code like below:
internal class SampleViewHolder(
private val activity: TheActivity,
view: View
) : RecyclerView.ViewHolder(view) {
fun bind(item: SampleInfo, position: Int) {
val view = itemView
...
bindLifecycle()
}
fun onViewRecycled() {
itemView.imv_sample.onViewRecycled(sampleAdapter.playStateStore)
activity.lifecycle.removeObserver(lifecycleObserver)
}
private val lifecycleObserver = object : LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
private fun onDestroy() {
MLog.info(TAG, "receive destroy event")
itemView.imv_sample.onHostActivityDestroyed()
}
}
private fun bindLifecycle() {
activity.lifecycle.run {
MLog.info(TAG, "success bind lifecycle")
addObserver(lifecycleObserver)
}
}
}
When the activity is destroyed, set the list adapter to null. This will make sure onViewDetachedFromWindow is called for all the views in list when the activity is destroyed.
#Override
public void onDestroy() {
mList.setAdapter(null);
super.onDestroy();
}
And then you can remove the callbacks from the handler running inside the viewHolder. This requires you to save the handler reference inside your viewHolder.
#Override
public void onViewDetachedFromWindow(#NonNull PurchaseItemViewHolder holder) {
if (holder.handler != null) {
holder.handler.removeCallbacksAndMessages(null);
}
super.onViewDetachedFromWindow(holder);
}
My MainActivity has a Thread that generates RSA keys and returns the amount of time in milliseconds that it took to generate them.
While I run this Thread, the app goes to a second Activity.
I need the second Activity to receive that time in milliseconds.
As I understand, once you call startActivity(), the parent Activity goes to sleep. So how can I run both simultaneously?
Thanks!
You can use LocalBroadcastReceiver.
public class SecondActivity extends AppCompatActivity {
private BroadcastReceiver mRsaReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
ArrayList<String> rsaList = intent.getStringArrayListExtra("rsaList");
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
LocalBroadcastManager.getInstance(this).registerReceiver(mRsaReceiver, new IntentFilter("RSA"));
}
#Override
protected void onDestroy() {
super.onDestroy();
LocalBroadcastManager.getInstance(this).unregisterReceiver(mRsaReceiver);
}
}
From Your rsa thread
Intent rsaIntent = new Intent("RSA");
rsaIntent.putExtra("rsaList", rsaArrayList);
LocalBroadcastManager.getInstance(context).sendBroadcast(rsaIntent);
As I understand, once you startActivity(), the parent activity goes to sleep. So how can I run both simultaneously
You do not. And you do not need to. Since you generate your RSA keys on a separate thread that code should be fully independent from your activities (aside from being started in MainActivity). So all you need to know is when your background task finished - and for that you can use either in-app broadcasts or use Event Bus like GreenRobot's EventBus library or use RxJava.
I have an activity that has 3 fragments on it with Tabs, one of them is called "TaskFragment".
In my main Activity i only load the fragments.
In TaskFragment i have a RecyclerView that is working fine and is showing the items as intended.
The problem comes, when i insert data using a DialogFragment, because it does insert data (i am using DbFlow ORM), but it does not (of course) refresh the adapter since it is in the TaskFragment fragment inside the DetailMainActivity activity as i said.
I have tried to use onResume() and onPause() in order to refresh the adapter, but they are never called since the activity does not get paused or in onresume for a DialogFragment.
I have tried aswell to use an interface, but it does not work and i have searched all over stackoverflow and google with no luck.
I leave here some of my code for you to understand better:
DetailMainActivity.java
Here in the onClick interface i show the DialogFragment to the user to input the information.
FragmentManager fm = getSupportFragmentManager();
AddSimpleTask sptask = new AddSimpleTask();
sptask.show(fm, "tag");
TaskFragment.java
In this fragment i have my RecyclerView
private void setupRecyclerView() {
mRecyclerView.setAdapter(adapter);
mRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 2));
mRecyclerView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (DetailMainActivity.FAB_Status) {
DetailMainActivity.hideFAB();
DetailMainActivity.FAB_Status = false;
}
return false;
}
});
}
private void setupAdapter() {
adapter = new DetailMainTaskAdapter(simpleTaskList, this);
}
AddSimpleTask
And this is my DialogFragment. I have set a setOnShowListener() in order to avoid the DialogFragment to get dismiss early.
#Override
public void onShow(DialogInterface dialogInterface) {
final AlertDialog dialog =(AlertDialog) getDialog();
if (dialog != null){
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
positiveButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mEditTextName.getText().toString().trim().isEmpty() ||
mEditTextContent.getText().toString().trim().isEmpty() ) {
if (mEditTextName.getText().toString().trim().isEmpty()) {
mEditTextName.setError("Can not be empty");
}
if (mEditTextContent.getText().toString().trim().isEmpty()) {
mEditTextContent.setError("Can not be empty");
}
}else {
presenter.beingInsertion(mEditTextName.getText().toString().trim(), mEditTextContent.getText().toString().trim()
, foreignId);
}
}
});
negativeButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dismiss();
}
});
}
}
If the insert is successfully achieved the onInsertSuccess method is called (i am using MVP)
#Override
public void onInsertSuccess() {
Snackbar.make(getActivity().findViewById(R.id.containerMainDetail), "Actividad agregada", Snackbar.LENGTH_SHORT).show();
dismiss();
}
I have called adapter.notifyDataSetChanged() in many places, and i also tried with a custom interface, but i can not make this work.
Sorry for the long post, but thanks in advance for your help.
There are some errors in your statement but I'll get to that later. notifyDataSetChanged() only notifies the adapter that the underlying list (or array) has changed. The implication is that you first need to requery your database and obtain the new list before calling notifyDataSetChanged() on the adapter else there is no point as the underlying list will still be the same and it will not update the adapter.
The correct way of calling this will be through your custom listener interface and not in the onPause()/onResume() callbacks as there is the possibility that the user does not enter a value and hence you will unnecessarily be querying the database. In your custom listener interface implementation, first update the list with the new data from the DB and then notify the adapter.
Which leads to the error in assumption that onPause()/onResume() callbacks do not happen when your Activity is covered by a DialogFragment - this is incorrect. The moment the activity view is even partially covered, the onPause() callback is triggered.
I have a Main activity and after click on button I start thread (but the thread is hidden in library and I have only callback in Main activity.
Now I want to start another activity (call A) where I want to put results from the thread.
Below is simplified code:
public class Main extends Activity {
XManager.ResultsCallback xResultsCallback = new XManager.ResultsCallback() {
// the method is called every 10 sec.
#Override
public void onResult(ArrayList<String> texts) {
}
};
XManager xManager = new xManager(xResultsCallback);
View.OnClickListener onClick = new View.OnClickListener() {
#Override
public void onClick(View arg0) {
XManager.start();
Intent i = new Intent(Main.this, A.class);
startActivity(i);
}
};
}
I want to update the content of A activity each time when onResult() method is called. How to do that?
Use LocalBroadcastManager,
In your Main Activity create function :
private void sendResult() {
Log.d("sender", "Broadcasting message");
Intent intent = new Intent("custom-event-name");
// You can also include some extra data.
intent.putExtra("message", "This is my result!");
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
and add BroadcastReceiver in your A Activity
private BroadcastReceiver onResult= new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Log.d("jazzy","onReceive called");
}
};
add on OnCreate
#Override
public void onCreate(Bundle savedInstanceState) {
// Register to receive messages.
LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,
new IntentFilter("custom-event-name"));
}
add onDestroy
#Override
protected void onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
super.onDestroy();
}
I have a suggestion that you should do as follows:
Start Your Activity A on button click
Inside Activity A declare your XManager instance with a callback present in A itself
Then start your XManager as XManager.start(); that way you would be getting all the callbacks in your desired activity.
Have a great day!
I think if you want to decouple the logic, beside you can use the Android BroadcastReceiver, the another flexible choice is to use the Bus
And you can integrate it with gradle easily
dependencies {
compile 'com.squareup:otto:+'
}
I tried making my first android app. It works well but strangely when I pause then resume the main activity (basically when showing the settings menu) the application FPS decrease a lot, and I've no clue why.
Here is my structure:
DrawView is a class implementing the View class with an "update" method which does the stuff at each frame (calculating and drawing).
The main activity create a DrawView (as content view) and with a Handler and a Runnable requests it to refresh every 10ms.
Here is the (simplified) code of Main.java:
public class Main extends Activity {
DrawView drawView;
private Handler myHandler;
private Runnable myRunnable = new Runnable() {
public void run() {
drawView.update();
myHandler.postDelayed(this, 10);
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
drawView = new DrawView(this);
setContentView(drawView);
myHandler = new Handler();
myHandler.post(myRunnable);
}
#Override
protected void onPause() {
super.onPause();
if(myHandler != null) myHandler.removeCallbacks(myRunnable);
}
#Override
protected void onResume() {
super.onResume();
if(myHandler != null) myHandler.post(myRunnable);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId())
{
case R.id.menu_close:
finish();
break;
case R.id.menu_settings:
Intent settingsActivity = new Intent(getBaseContext(), Preferences.class);
startActivity(settingsActivity);
}
return super.onOptionsItemSelected(item);
}
}
By implementing a FPS counter in the DrawView.update method, I noticed that the FPS drop from 100+ at the begining to a ceiling of 60 after opening then closing the preferences Activity.
I don't understand where is my mistake.
The reason is simple: you post your Runnable object twice (in onCreate and onResume methods) and DrawView.update() actually will be called more often than once per 10 ms.
When Activity is paused you call myHandler.removeCallbacks(myRunnable) and it removes BOTH previously added objects. After Activity is resumed you post the Runnable again but only once (because 'onCreate' is not called).