Is there any way to trigger the SwipeRefreshLayout programmatically? The animation should start and the onRefresh method from the OnRefreshListener interface should get called.
if you are using the new swipeRefreshLayout intoduced in 5.0
As the image shown above you just need to add the following line to trigger the swipe refresh layout programmatically
Work
in Java:
mSwipeRefreshLayout.post(new Runnable() {
#Override
public void run() {
mSwipeRefreshLayout.setRefreshing(true);
}
});
on in Kotlin:
mSwipeRefreshLayout.post { mSwipeRefreshLayout.isRefreshing = true }
NOT work
if you simply call in Java:
mSwipeRefreshLayout.setRefreshing(true);
or in Kotlin
mSwipeRefreshLayout.isRefreshing = true
it won't trigger the circle to animate, so by adding the above line u just make a delay in the UI thread so that it shows the circle animation inside the ui thread.
By calling mSwipeRefreshLayout.setRefreshing(true) the OnRefreshListener will NOT get executed
In order to stop the circular loading animation call mSwipeRefreshLayout.setRefreshing(false)
In order to trigger SwipeRefreshLayout I tried this solution:
SwipeRefreshLayout.OnRefreshListener swipeRefreshListner = new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
Log.i(TAG, "onRefresh called from SwipeRefreshLayout");
// This method performs the actual data-refresh operation.
// The method calls setRefreshing(false) when it's finished.
loadData();
}
};
Now key part:
swipeLayout.post(new Runnable() {
#Override public void run() {
swipeLayout.setRefreshing(true);
// directly call onRefresh() method
swipeRefreshListner.onRefresh();
}
});
Simply create a SwipeRefreshLayout.OnRefreshListener and call its function onRefresh() whenever needed:
SwipeRefreshLayout srl;
SwipeRefreshLayout.OnRefreshListener refreshListener;
srl = (SwipeRefreshLayout)v.findViewById(R.id.swipeRefreshLayout);
refreshListener = new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
//Do your stuff here
}
};
srl.setOnRefreshListener(refreshListener);
Now, whenever you want to call it manually, just call it through the listener
refreshListener.onRefresh();
Bit late to the thread, but you do not need to launch a Runnable to do this. You can simply trigger the refresh and call your onRefresh method directly in onCreate, or wherever you want this to happen:
class MyFragment: SwipeRefreshLayout.OnRefreshListener {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initViews()
}
fun initViews() {
swipe_refresh_layout?.apply {
setOnRefreshListener(this#MyFragment)
isRefreshing = true
onRefresh()
}
}
override fun onRefresh() {
// Do my refresh logic here
}
}
binding.swipeRefreshLayout.setRefreshing(true); // show loading
binding.swipeRefreshLayout.post(this::updateUI); // call method
binding.swipeRefreshLayout.setOnRefreshListener(this::updateUI); // call method
You can call onRefresh() method programmatically and then inside the method start the animation if it is not already started. See the following:
#Override
public void onRefresh() {
if (!mSwipeRefreshLayout.isRefreshing()) mSwipeRefreshLayout.setRefreshing(true);
//TODO
}
Just to force in add this two to ennable swipe gesture
swipeRefreshLayout.setOnRefreshListener(
new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
Log.i(TAG, "onRefresh called from SwipeRefreshLayout");
// This method performs the actual data-refresh operation.
// The method calls setRefreshing(false) when it's finished.
FetchData();
}
}
);
Related
I programmed a Vocabulary Trainer with Vocabulary Cards. The Vocabulary Cards are Entries in a Room Database created from an asset. I am displaying these Vocabulary Cards with ViewPager2 in an Activity. I have a 'correct' and a 'false' button and when the user clicks on either, I want to update the Vocabulary Card (-> The entry in the sqlite database) and automatically swipe to the next item of the ViewPager2.
If I implement the buttons in the ViewPager2Adapter, I can't find a way to change the position of the ViewPager2. If I implement the buttons in the activity the sqlite entry does not update properly (After it updates the entry, the activity is constantly refreshed, it seems like it never the leaves the OnClick methode of the button).
So is it possible to change the position of ViewPager2 from inside the ViewPager2Adpater?
Thanks for your help!
That is the relevant code if I have the buttons in my ViewPager2Adapter. Here I don't know how to change the position of the ViewPager2
public void onBindViewHolder(#NonNull #NotNull ViewHolder holder, int position) {
VocabularyCard vocabularyCard = currentCards.get(position);
holder.btn_correct.setOnClickListener(view -> {
vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard);
});
holder.btn_false.setOnClickListener(v15 -> {
vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard);
});
That is the relevant code if I have the buttons in the Activity. Here the update function triggers an infinite updating of the Activity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
initAll();
btn_correct_2.setOnClickListener(view -> {
int currentPos = viewpager2.getCurrentItem();
vocabularyViewModel.getCurrentCards().observe(this, vocabularyCards -> {
if (vocabularyCards.size() == currentPos){
Intent intent = new Intent(TestActivity.this, MainActivity.class);
startActivity(intent);
}else {
viewpager2.setCurrentItem(currentPos + 1);
}
VocabularyCard vocabularyCard = vocabularyCards.get(currentPos);
vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard);
});
});
btn_false_2.setOnClickListener(view -> {
int currentPos = viewpager2.getCurrentItem();
vocabularyViewModel.getCurrentCards().observe(this, vocabularyCards -> {
if (vocabularyCards.size() == currentPos){
Intent intent = new Intent(TestActivity.this, MainActivity.class);
startActivity(intent);
}else {
viewpager2.setCurrentItem(currentPos + 1);
}
VocabularyCard vocabularyCard = vocabularyCards.get(currentPos);
vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard);
});
});
Objects.requireNonNull(getSupportActionBar()).setTitle(getResources().getString(R.string.learn_new_words));
LiveData<List<VocabularyCard>> allNewCards = vocabularyViewModel.getAllNewCards(goal);
allNewCards.observe(this, vocabularyCards -> vocabularyViewModel.setCurrentCards(vocabularyCards));
vocabularyViewModel.getCurrentCards().observe(this, vocabularyCards -> {
viewPager2Adapter.setCurrentCards(vocabularyCards);
viewpager2.setAdapter(viewPager2Adapter);
viewpager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
}
#Override
public void onPageSelected(int position) {
super.onPageSelected(position);
}
#Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
}
});
});
The update function in the Room DAO is straightforward:
#Update
void updateSingleVocabularyCard(VocabularyCard vocabularyCard);
I left out all the code that is not relevant.
There are several ways to propagate an event from the adapter to the activity where you manage your cards using ViewPager2. Let's have a look how it can be done either using an interface or using the same view model. But in any case I strongly recommend you to update your database in a background thread to prevent any possible UI lags.
1. Using an interface
This option is more flexible since you can propagate events as well as pass data as parameters. You can also reuse this interface for other cases. As far as I See you have a holder that has 2 buttons for the users to make choices. So our event here would be something like ChoiceEventListener, let's call this interface like so. Then you'd have to add a method to handle this event from within anywhere you wanna hear this event, and let's call its handle method onChoice(). Finally we would need a variable to indicate what the choice is. Now that ready to implement, let's write the new interface...
ChoiceEventListener.java
public interface ChoiceEventListener {
void onChoice(VocabularyCard vocabularyCard, boolean choice);
}
The next thing to do is to implement this interface where you want to listen to this event. In this case it is in your activity. There are 2 ways to do this:
You make your activity to inherit its methods using the implements keyword
YourActivity.java
public class YourActivity extends AppCompatActivity implements ChoiceEventListener {
// Use a background thread for database operations
private Executor executor = Executors.newSingleThreadExecutor();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
initAll();
// You must construct your adapter class with the listener
ViewPager2Adapter adapter = new ViewPager2Adapter(/* Other params... */, this);
}
#Override
public void onChoice(VocabularyCard vocabularyCard, boolean choice) {
if(choice) {
// User pressed the correct button
}
else {
// User pressed the false button
}
// Update card in the background
executor.execute(()-> vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard));
}
}
You can implement it as an anonymous function
YourActivity.java
public class YourActivity extends AppCompatActivity {
// Use a background thread for database operations
private Executor executor = Executors.newSingleThreadExecutor();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
initAll();
// You must construct your adapter class with the listener
ViewPager2Adapter adapter = new ViewPager2Adapter(/* Other params... */, (vocabularyCard, choice) -> {
if(choice) {
// User pressed the correct button
}
else {
// User pressed the false button
}
// Update card in the background
executor.execute(()-> vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard));
});
}
}
Finally the ViewPager2Adapter class implementation would be something like this:
ViewPager2Adapter.java
public class ViewPager2Adapter extends RecyclerView.Adapter<ViewPager2ViewHolder> {
// Here is your listener to deliver the choice event to it
private final ChoiceEventListener listener;
// Constructor
public ViewPager2Adapter(/* Other params... */, ChoiceEventListener listener) {
/* Other inits */
this.listener = listener;
}
public void onBindViewHolder(#NonNull #NotNull ViewHolder holder, int position) {
VocabularyCard vocabularyCard = currentCards.get(position);
holder.btn_correct.setOnClickListener(view -> {
listener.onChoice(vocabularyCard, true); // true for correct
});
holder.btn_false.setOnClickListener(v15 -> {
listener.onChoice(vocabularyCard, false); // false for false :)
});
}
}
2. Use the ViewModel for inter-communication
In this option we use a LiveData object to make page switching. The only thing you need to know in your activity is the current position which you get it from the adapter class. Once you update it in the adapter, set the current position value in live data so that you can switch the page in your activity.
VocabularyViewModel.java
public class VocabularyViewModel extends ViewModel {
public MutableLiveData<Integer> mldCurrentPosition = new MutableLiveData<>(0);
}
YourActivity.java
public class YourActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
initAll();
vocabularyViewModel.mldCurrentPosition().observe(this, currentPosition -> {
if(currenPosition == null) return; // ignore when null
viewpager2.setCurrentItem(currentPosition + 1);
}
}
}
Finally the ViewPager2Adapter class implementation would be something like this:
ViewPager2Adapter.java
public class ViewPager2Adapter extends RecyclerView.Adapter<ViewPager2ViewHolder> {
// Use a background thread for database operations
private Executor executor = Executors.newSingleThreadExecutor();
public void onBindViewHolder(#NonNull #NotNull ViewHolder holder, int position) {
VocabularyCard vocabularyCard = currentCards.get(position);
holder.btn_correct.setOnClickListener(view -> {
// Update card in the background
executor.execute(()-> vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard));
// Then invoke switching to the next card
vocabularyViewModel.mldCurrentPosition.setValue(position + 1);
});
holder.btn_false.setOnClickListener(v15 -> {
// Update card in the background
executor.execute(()-> vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard));
// Then invoke switching to the next card
vocabularyViewModel.mldCurrentPosition.setValue(position + 1);
});
}
}
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);
}
Hi so I have the following code:
In the onCreate:
mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.activity_main_swipe_refresh_layout);
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
refreshContent();
}
});
And in another method:
private void refreshContent(){
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
db = new DatabaseHelper(getApplicationContext());
new RefreshScores().execute();
list.clear();
list.addAll(db.getTracked());
db.closeDB();
mSwipeRefreshLayout.setRefreshing(false);
adapter.notifyDataSetChanged();
}
},2000);
}
Currently, when I pull down near the top of my app, the refresh spinner shows up and starts spinning. It disappears after 2 seconds. However, I want it to remain visible until RefreshScores().execute() and list.clear(); list.addAll(db.getTracked()); db.closeDB(); have occured. The RefreshScores is an AsyncTask. Not really sure how to do this.
AsyncTask has a callback named onPostExecute. This callback will be invoked in the UI thread when doInBackground() returns. See http://developer.android.com/reference/android/os/AsyncTask.html
You may try using
mSwipeRefreshLayout.setRefreshing(false);
directly in this callback, and move other calls which require new data to the callback as well.
Edit
mSwipeRefreshLayout.post(new Runnable() {
#Override
public void run() {
mSwipeRefreshLayout.setRefreshing(false);
}
});
I have a simple AlertDialog that displays a list of some items and upon clicking one of them, the clicked item is passed back to the enclosing Activity. I also want to perform some default handling when the user cancels the dialog (using the back button) - more specifically, I want to pass an empty string to the activity in such case.
However, if I put the dialog in a DialogFragment (from the compatibility package), the OnCancelListener is not called when I close the dialog with the back button. What am I doing wrong?
public class SelectItemDialog extends DialogFragment {
public interface Callback {
void onItemSelected(String string);
}
private static final String ARG_ITEMS = "items";
private Callback callback;
public static SelectItemDialog fromItems(Collection<String> items) {
SelectItemDialog fragment = new SelectItemDialog();
fragment.setArguments(newArguments(items));
return fragment;
}
private static Bundle newArguments(Collection<String> items) {
Bundle arguments = new Bundle();
arguments.putStringArray(ARG_ITEMS, items.toArray(new String[items.size()]));
return arguments;
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
callback = (Callback) activity;
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final String[] items = getArguments().getStringArray(ARG_ITEMS);
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.dialog_select_email_title)
.setItems(items, new OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
callback.onItemSelected(items[which]);
}
})
.setOnCancelListener(new OnCancelListener() {
#Override
public void onCancel(DialogInterface dialog) {
// this code is not executed
callback.onItemSelected("");
throw new RuntimeException("dialog cancelled");
}
})
.create();
}
}
It might have to do with the fact that there is no explicit call to cancel() from your code.
The OnCancelListener documentation says:
This will only be called when the dialog is canceled
Which probably needs an explicit cancel() call.
Either make a positive/negative button with a OnClickListener that calls DialogInterface#cancel() or use a OnDismissListener() with an extra check to see if a list item was clicked.
Also, to listen for a back keypress and cancel the dialog, you can set up an OnKeyListener, like outlined in this SO answer
Also, once you have the Dialog set up, it would also be a good idea to use Dialog#setCanceledOnTouchOutside() in case the the user taps outside the Dialog.
Edit: The below part is the easy way to handle cancel events in a DialogFragment.
Since you are using a DialogFragment, this class has a very handy method, DialogFragment#onCancel() which gets called when the DialogFragment is cancelled. Do your logic in there.
DialogFragments are more complex, with a slightly different lifecycle than normal dialogs. Therefore, first check the documentation if you have a certain Dialog-based approach that you are trying to port to a DialogFragment, some methods may exist that allow your new implementation to function properly!
If you are using DialogFragment and want to listen back button then use this -
this.getDialog().setOnKeyListener(new Dialog.OnKeyListener() {
#Override
public boolean onKey(DialogInterface dialog, int keyCode,
KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (****) {
your logic
}
return true;
}
return false;
}
});
Note: DialogFragment own the Dialog.setOnCancelListener and Dialog.setOnDismissListener callbacks. You must not set them yourself.
To find out about these events, override onCancel(DialogInterface) and onDismiss(DialogInterface).
public class SelectItemDialog extends DialogFragment {
#Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
//your code hear
dialog.cancel();
}
}
And you should remove .setOnCancelListener()
Actually if you want to use DialogFragment, you can never add OnCancelListener or OnDismissListener to it, since the Dialog Fragment owns callbacks to these methods!
You have 3 options here:
1- go with regular dialogs.2- set your dialog fragment to cancellable(false) and add a cancel button to the dialog.3- check #Nikhil Pingle answer.
this is from the documentation of the Dialog Fragment
* <p><em>Note: DialogFragment own the {#link Dialog#setOnCancelListener
* Dialog.setOnCancelListener} and {#link Dialog#setOnDismissListener
* Dialog.setOnDismissListener} callbacks. You must not set them yourself.</em>
* To find out about these events, override {#link #onCancel(DialogInterface)}
* and {#link #onDismiss(DialogInterface)}.</p>
Cancel Listener or Dismiss listener in DialogFragment can achieve by onDismiss
DialogFragment newFragment = new DatePickerFragment();
newFragment.show(getFragmentManager(), "datePicker");
newFragment.onDismiss(new DialogInterface(){
#Override
public void cancel() {
// TODO Auto-generated method stub
}
#Override
public void dismiss() {
// TODO Auto-generated method stub
}
});
I have a button that I don't want to be clickable until a certain amount of time has run (say, 5 seconds?) I tried creating a thread like this
continueButtonThread = new Thread()
{
#Override
public void run()
{
try {
synchronized(this){
wait(5000);
}
}
catch(InterruptedException ex){
}
continueButton.setVisibility(0);
}
};
continueButtonThread.start();
But I can't modify the setVisibility property of the button within a different thread. This is the error from the LogCat:
10-02 14:35:05.908: ERROR/AndroidRuntime(14400): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
Any other way to get around this?
The problem is that you can touch views of your activity only in UI thread. you can do it by using runOnUiThread function. I would like to suggest you to use
handler.postDelayed(runnable, 5000)`
You must update your view from UI-thread. What you are doing is you are updating from non-ui-thread.
Use
contextrunOnUiThread(new Runnable(){
#Override
public void run() {
// TODO Auto-generated method stub
}});
or use handler and signalize hand.sendMessage(msg) when you think is right time to update the view visibility
Handler hand = new Handler()
{
#Override
public void handleMessage(Message msg) {
/// here change the visibility
super.handleMessage(msg);
}
};
You can use the postDelayed method from the View class (A Button is a child of View)
here is simple answer i found
Button button = (Button)findViewBYId(R.id.button);
button .setVisibility(View.INVISIBLE);
button .postDelayed(new Runnable() {
public void run() {
button .setVisibility(View.VISIBLE);
}
}, 7000);