My application should show several timers and start them at the same time.
The problem is that these don't run in sync. You can see this on the GIF, you can see that they lose synchronicity.
After a certain time you can see that a timer is updated faster than the others. First timer 1 is displayed, followed by timer 2 with a delay and then again with a certain delay timer 3.
I want to create multiple timers and each timer has a different duration.
I want to display these timers in a TextView. I initialize this in an ArrayList in the MainActivity. These are then added in a ReyclerView so that they are also displayed. To start, I go through a for loop and execute the startTimer() method for each individual object. Unfortunately, the times are not correct for every timer. That means one timer is faster, the second is slower and so on. So each timer starts at different times or the text changes at different times.
Have only one timing item (e.g., ScheduledExecutorService) in a
viewmodel, not 10 in an activity. While you may want to depict 10
times, in the UI, you only need one timing item to tell you when a
second has elapsed. Have the viewmodel emit details of the time values
that should be rendered in the UI. Have the UI simply display those
time values
This text on top was an answer. How could I build this in so that I only have one timer element and it controls the times? How can I let these timers run synchronously?
Each timer should be updated and displayed at the same time.
I look forward to an answer, thank you in advance for your support!
MainActivity
public class MainActivity extends AppCompatActivity implements ModelTimer.MyCallback {
private RecyclerView recyclerView;
private Button button_start;
private Dialog epicDialog;
public static ArrayList<ModelTimer> timerList;
public static TimerAdapter adapter;
public static Context context;
#Override
protected void onCreate(Bundle savedInstanceState) {
recyclerView = findViewById(R.id.recyclerview_timers);
button_start = findViewById(R.id.button_start);
timerList= new ArrayList<>();
for (int i = 1; i <= 10; i++) {
timerList.add(new Timer(i, 600000 * i))); //Each timer has a different time
}
recyclerView.setLayoutManager(recyclerViewLayoutManager);
adapter = new TimerAdapter(this, timerList);
recyclerView.setAdapter(adapter);
context = this;
button_start.setOnClickListener(v -> {
for (Timer timer: timerList) {
timer.startTimer();
});
#Override
public void updateMyText(int index, long time) {
timerList.get(index-1).setTime(time);
adapter.notifyDataSetChanged();
}
}
#Override
public void updateMyText(int index, long time) {
timerList.get(index-1).setTime(time);
adapter.notifyDataSetChanged();
}
}
ModelTimer
public class ModelTimer {
public interface MyCallback {
public void updateMyText(int index, long time);
}
private int index;
private long time;
private CountDownTimer mCountDownTimer;
private boolean mTimerRunning;
private long startTime;
private long mTimeLeftInMillis;
private String timeLeftFormatted;
private MyCallback myCallback = null;
public ModelTimer(int index, long startTimeMilliseconds, MyCallback callback) {
this.index = index;
this.time = startTimeMilliseconds;
mTimeLeftInMillis = startTimeMilliseconds;
startTime = startTimeMilliseconds;
this.myCallback = callback;
}
public void startTimer() {
mCountDownTimer = new CountDownTimer(mTimeLeftInMillis, 1000) {
#Override
public void onTick(long millisUntilFinished) {
mTimeLeftInMillis = millisUntilFinished;
updateCountDownText();
}
#Override
public void onFinish() {
mTimerRunning = false;
}
}.start();
mTimerRunning = true;
}
public void resetTimer() {
mCountDownTimer.cancel();
mTimerRunning = false;
mTimeLeftInMillis = startTime;
timeLeftFormatted = formattedTime(startTime);
changeText(index-1);
}
public void updateCountDownText() {
//System.out.println("ID: " + getIndex() + " " + mTimeLeftInMillis);
if(myCallback != null) {
myCallback.updateMyText(getIndex(), mTimeLeftInMillis);
}
}
private void changeText(int element) {
String[] data = timeLeftFormatted.split(":");
int hours = Integer.parseInt(data[0]);
int minutes = Integer.parseInt(data[1]);
int seconds = Integer.parseInt(data[2]);
int timeSeconds = seconds + 60 * minutes + 3600 * hours;
long milliseconds = TimeUnit.MILLISECONDS.convert(timeSeconds, TimeUnit.SECONDS);
MainActivity.countdownList.get(element).setTime(milliseconds);
MainActivity.adapter.notifyDataSetChanged();
}
public static long getMilliseconds(String time) {
String[] data = time.split(":");
int hours = Integer.parseInt(data[0]);
int minutes = Integer.parseInt(data[1]);
int seconds = Integer.parseInt(data[2]);
int timeSeconds = seconds + 60 * minutes + 3600 * hours;
return TimeUnit.MILLISECONDS.convert(timeSeconds, TimeUnit.SECONDS);
}
public static String formattedTime(long time) {
int milliToSec = (int) (time / 1000);
int hours = milliToSec / 3600;
int minutes = (milliToSec / 60) % 60;
int seconds = milliToSec % 60;
return String.format(Locale.getDefault(), "%02d:%02d:%02d", hours, minutes, seconds);
}
public int getIndex() {
return this.index;
}
public void setIndex(int index) {
this.index = index;
}
public long getTime() { return this.time; }
public void setTime(long time) {
this.time = time;
}
}
TimerAdapter
public class TimerAdapter extends RecyclerView.Adapter<TimerAdapter.ViewHolder> {
private Context mContext;
private ArrayList<ModelTimer> list;
static String indexCountdown;
TimerAdapter(Context context, ArrayList<ModelTimer> list) {
mContext = context;
this.list = list;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
View view = layoutInflater.inflate(R.layout.recyclerview_countdowns, parent, false);
ViewHolder viewHolder = new ViewHolder(view);
return viewHolder;
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
ModelTimer timerItemList = list.get(position);
TextView index = holder.countdown_index;
TextView time = holder.countdown_time;
CardView layout = holder.layout_countdowns;
index.setText(timerItemList .getIndex() +"");
time.setText(ModelTimer.formattedTime(timerItemList .getTime()));
}
#Override
public int getItemCount() {
return list.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView countdown_index, countdown_time;
private CardView layout_countdowns;
public ViewHolder(#NonNull View itemView) {
super(itemView);
countdown_index = itemView.findViewById(R.id.coutdown_index);
countdown_time = itemView.findViewById(R.id.countdown_time);
layout_countdowns = itemView.findViewById(R.id.layout_countdown);
}
}
}
You can try it with CountDownTimer if you want. It would look like this:
public class TimerHelper {
public interface UpdateCallback {
/**
* #return true if want to be updated again false otherwise
*/
boolean secondPassedCallback();
}
private ArrayList<UpdateCallback> callbacksList = new ArrayList<>();
/**
* #param maxTime maximum of all timers
*/
public void start(int maxTime) {
CountDownTimer mCountDownTimer = new CountDownTimer(maxTime, 1000) {
#Override
public void onTick(long millisUntilFinished) {
ArrayList<UpdateCallback> itemsToRemove=new ArrayList<>();
for (UpdateCallback updateCallback : callbacksList) {
boolean wantToBeUpdated = updateCallback.secondPassedCallback(); // remove timers that reached 0
if (!wantToBeUpdated) {
itemsToRemove.add(updateCallback);
}
}
callbacksList.removeAll(itemsToRemove);
}
#Override
public void onFinish() {
}
};
mCountDownTimer.start();
}
public boolean addCallback(UpdateCallback callback) {
return callbacksList.add(callback);
}
public boolean removeCallback(UpdateCallback callback) {
return callbacksList.remove(callback);
}
}
With this just make your ModelTimer implement this callback and do the UI update there. Finally in activity change button_start OnClickListener to:
button_start.setOnClickListener(v -> {
TimerHelper t = new TimerHelper();
for (ModelTimer timer : timerList) {
t.addCallback(timer);
}
});
You can in your ReyclerView ViewHolder instead of inside in ModelTimer
chronometer.setBase(new Date().getTime());
chronometer.setOnChronometerTickListener(new OnChronometerTickListener() {
public void onChronometerTick(Chronometer cArg) {
long t = SystemClock.elapsedRealtime() - cArg.getBase();
cArg.setText(DateFormat.format("HH:mm:ss", t));
}
});
chronometer.start();
Related
There is Scroll by loading RecyclerView(Lazy loading). When an item quantity is incremented by adding the plus button to say 10. after loading the next set of items while scrolling, The item quantity(10) is reset to 1 and 10 will be displayed for another item.
In the same sequence, if the unit is changed(by the spinner) to another unit the corresponding image will be loaded but after scrolling the unit and image will change back to initial unit.
When the image of an item is clicked, a dialog will show the details of the selected item . Sometimes the image in the dialog is correct but image of the RecyclerView is incorrect, will take time to load or will not load.
public class ArticleRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
implements Filterable {
private static final String TAG = "ArticleAdapter";
private final int VIEW_TYPE_ITEM = 0;
private final int VIEW_TYPE_LOADING = 1;
private Context context;
private Dialog articleDescriptionDialog;
private UOMOffersLIistViewAdapter uomOffersLIistViewAdapter;
private List<ArticleDetails> articleDetailsList;
private HomeActivityViewModel homeActivityViewModel;
private ItemFilter itemFilter = new ItemFilter();
private GetViewListener getViewListener;
private int lastPosition = -1;
private OnLoadMoreListener loadMoreListener;
private boolean isLoading = false, isMoreDataAvailable = true;
/*
* isLoading - to set the remote loading and complete status to fix back to back load more call
* isMoreDataAvailable - to set whether more data from server available or not.
* It will prevent useless load more request even after all the server data loaded
* */
public ArticleRecyclerViewAdapter(Context context, List<ArticleDetails> articleDetailsList
, HomeActivityViewModel homeActivityViewModel, Fragment fragment) {
this.context = context;
this.articleDetailsList = articleDetailsList;
this.homeActivityViewModel = homeActivityViewModel;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(context);
if (viewType == VIEW_TYPE_ITEM) {
return new ItemViewHolder(inflater.inflate(R.layout.recycler_view_article_layout, parent, false));
} else {
return new LoadHolder(inflater.inflate(R.layout.item_loading_layout, parent, false));
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
if (position >= getItemCount() - 1 && isMoreDataAvailable && !isLoading && loadMoreListener != null) {
isLoading = true;
loadMoreListener.onLoadMore();
}
if (getItemViewType(position) == VIEW_TYPE_ITEM) {
populateItemRows((ItemViewHolder) viewHolder, position);
}
//No else part needed as load holder doesn't bind any data
}
#Override
public int getItemViewType(int position) {
if (articleDetailsList.get(position).isLoading()) {
return VIEW_TYPE_LOADING;
} else {
return VIEW_TYPE_ITEM;
}
}
#Override
public int getItemCount() {
return articleDetailsList.size();
}
#Override
public long getItemId(int position) {
return position;
}
/* VIEW HOLDERS */
static class ItemViewHolder extends RecyclerView.ViewHolder {
private ImageView stockImageView, copyStockImageView;
private TextView articleNameTextView, articleStockAmountTextView, articleOfferTextView, addCartTextView;
private NumberPicker articleStockQuantityNumberPicker;
private Spinner articleStockSpinner;
private ImageButton offerInfoImageButton;
private int selectedStockItemPosition, stockQuantity;
private String articleStockQuantityString = "";
private Integer stockId;
private double stockAmount;
public ItemViewHolder(View itemView) {
super(itemView);
stockImageView = itemView.findViewById(R.id.stockImgv);
copyStockImageView = itemView.findViewById(R.id.copyStockImgv);//for fly to cart animation
articleNameTextView = itemView.findViewById(R.id.articleNameTxt);
articleStockAmountTextView = itemView.findViewById(R.id.stock_amount_txt);
articleOfferTextView = itemView.findViewById(R.id.offer_txt);
offerInfoImageButton = itemView.findViewById(R.id.offerInfo_imgbtn);
addCartTextView = itemView.findViewById(R.id.addCart_txt);
articleStockSpinner = itemView.findViewById(R.id.stock_spnr);
articleStockQuantityNumberPicker = itemView.findViewById(R.id.qty_numberPicker);
articleStockQuantityNumberPicker.setMin(1);
articleStockQuantityNumberPicker.setUnit(1);
// no limit.
// viewHolder.quantityNumberPicker.setMax(15);
}
}
static class LoadHolder extends RecyclerView.ViewHolder {
public LoadHolder(View itemView) {
super(itemView);
}
}
public void setMoreDataAvailable(boolean moreDataAvailable) {
isMoreDataAvailable = moreDataAvailable;
}
/**
* notifyDataSetChanged is final method so we can't override it
* call articleRecyclerViewAdapter.notifyDataChanged(); after update the list
*/
public void notifyDataChanged() {
notifyDataSetChanged();
isLoading = false;
}
public interface OnLoadMoreListener {
void onLoadMore();
}
public void setLoadMoreListener(OnLoadMoreListener loadMoreListener) {
this.loadMoreListener = loadMoreListener;
}
private void populateItemRows(ItemViewHolder itemViewHolder, int itemPos) {
int itemPosition = itemViewHolder.getAdapterPosition();
try {
itemViewHolder.copyStockImageView.setImageResource(R.drawable.error_logo);//initialize with error logo.
itemViewHolder.stockImageView.setImageResource(R.drawable.error_logo);//initialize with error logo.
} catch (Exception e) {
e.printStackTrace();
}
itemViewHolder.articleNameTextView.setText(articleDetailsList.get(itemPosition).getArticleName());
itemViewHolder.articleNameTextView.setSelected(true);//for marquee
itemViewHolder.articleOfferTextView.setSelected(true);//for marquee
itemViewHolder.articleStockAmountTextView.setSelected(true);//for marquee
itemViewHolder.articleOfferTextView.setVisibility(View.GONE);//for demo (now no offer and schemes).
itemViewHolder.offerInfoImageButton.setVisibility(View.GONE);//for demo
setStockSpinner(itemViewHolder, itemPosition);
itemViewHolder.offerInfoImageButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//setUOMOffersDIalog(articleDetailsList.get(itemPosition));
}
});
itemViewHolder.articleStockQuantityNumberPicker.setValueChangedListener(new ValueChangedListener() {
#Override
public void valueChanged(int quantity, ActionEnum action) {
itemViewHolder.articleStockQuantityString = Integer.valueOf(quantity).toString();
}
});
itemViewHolder.addCartTextView.setOnClickListener(new SingleClickListener() {
#Override
public void onSingleClick(View v) {
itemViewHolder.articleStockQuantityString = Integer.valueOf(itemViewHolder.articleStockQuantityNumberPicker.getValue()).toString();
itemViewHolder.stockQuantity = Integer.valueOf(itemViewHolder.articleStockQuantityString);
itemViewHolder.stockAmount = articleDetailsList.get(itemPosition).getStockDetailsList()
.get(itemViewHolder.articleStockSpinner.getSelectedItemPosition()).getmRP();
itemViewHolder.stockId = articleDetailsList.get(itemPosition).getStockDetailsList()
.get(itemViewHolder.articleStockSpinner.getSelectedItemPosition()).getStockId();
Double stockDiscountAmount = 0.0;
//TODO: 26-Oct-19 discount hardcoded.
if (itemViewHolder.stockQuantity > 0) {
addItemToCart(itemViewHolder, itemViewHolder.stockId, itemViewHolder.stockQuantity, itemViewHolder.stockAmount, stockDiscountAmount);
} else {
//not using (used for when user enter invalid quantity)
MDToast.makeText(context, FinalVariables.ToastMessages.VALID_QUANTITY
, MDToast.LENGTH_SHORT, MDToast.TYPE_WARNING).show();
}
}
});
itemViewHolder.stockImageView.setOnClickListener(new SingleClickListener() {
#Override
public void onSingleClick(View v) {
itemViewHolder.articleOfferTextView.setVisibility(View.VISIBLE);
itemViewHolder.articleOfferTextView.setText( ""+ itemViewHolder.stockQuantity);
DialogUtils.showArticleDescription(context, articleDetailsList.get(itemPosition)
, itemViewHolder.selectedStockItemPosition, new DialogButtonClickListener() {
#Override
public void positiveButtonClick() {
//do nothing.
}
});
}
});
// Here you apply the animation when the view is bound
setAnimation(itemViewHolder.itemView, itemPosition);
}
private void setStockSpinner(ItemViewHolder itemViewHolder, int itemPosition) {
UOMSpinnerAdapter uomSpinnerAdapter = new UOMSpinnerAdapter(context, R.layout.spinner_uom_layout
, StringUtils.makeUomStringArrayForSpinner(articleDetailsList.get(itemPosition).getStockDetailsList()));
itemViewHolder.articleStockSpinner.setAdapter(uomSpinnerAdapter);
// Setting OnItemClickListener to the Spinner
itemViewHolder.articleStockSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
itemViewHolder.selectedStockItemPosition = position;
itemViewHolder.articleStockAmountTextView.setText(StringUtils.addAED(StringUtils.round3(
+articleDetailsList.get(itemPosition)
.getStockDetailsList().get(position).getmRP())));
//show image from link
try {
if (!articleDetailsList.get(itemPosition).getStockDetailsList().
get(position).getStockImageDetailsList().isEmpty()) {
ImageUtils.setGlide(context, itemViewHolder.stockImageView
, articleDetailsList.get(itemPosition).getStockDetailsList().
get(position).getStockImageDetailsList().get(0).getStockImage());
//same as stockImageView but used to animate fly to cart
ImageUtils.setGlide(context, itemViewHolder.copyStockImageView
, articleDetailsList.get(itemPosition).getStockDetailsList().
get(position).getStockImageDetailsList().get(0).getStockImage());
} else {
}
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
if (articleDetailsList.get(itemPosition).getStockDetailsList().size() > 0)
itemViewHolder.articleStockAmountTextView.setText(StringUtils.addAED(
StringUtils.round3(+articleDetailsList.get(itemPosition).getStockDetailsList().get(0).getmRP())));
itemViewHolder.selectedStockItemPosition = itemViewHolder.articleStockSpinner.getSelectedItemPosition();
}
});
}
private void addItemToCart(ItemViewHolder itemViewHolder, Integer stockId, Integer stockQuantity
, Double stockAmount, Double stockDiscountAmount) {
homeActivityViewModel.isItemAlreadyCarted(stockId
, new RetrieveDataListner() {
#Override
public void onDone(Object object) {
boolean isItemAlreadyCarted = (boolean) object;
if (!isItemAlreadyCarted) {//item not in cart,add to cart.
CartedItems cartedItem = new CartedItems();
cartedItem.setStockId(stockId);
cartedItem.setStockQuantity(stockQuantity);
cartedItem.setStockAmount(stockAmount);
cartedItem.setTotalAmount(CalcUtils.calculateTotalAmount(stockQuantity, stockAmount));
cartedItem.setNetAmount(CalcUtils.calculateNetAmount(stockQuantity
, stockAmount, stockDiscountAmount));
homeActivityViewModel.insertCartedItem(cartedItem
, new InsertDbSuccessListner() {
#Override
public void onSuccess() {
//set view to call back method for fly to cart animation
if (getViewListener != null){
getViewListener.onGetView(itemViewHolder.copyStockImageView);
}
itemViewHolder.articleStockQuantityNumberPicker.setValue(1);//reset item quantity in ui.
// TODO: 21-Mar-19 check fly to cart only in home activity or not
if (context instanceof MainActivity) {
MDToast.makeText(context, TextMessages.ITEM_ADDED
, MDToast.LENGTH_SHORT, MDToast.TYPE_SUCCESS).show();
} else if (context instanceof HomeActivity) {
try {
((HomeActivity) context).setCartDetails();
} catch (Exception e) {
e.printStackTrace();
}
}
}
#Override
public void onFailure() {
}
});
} else {//item already carted,asking to add new quantity to carted quantity.
DialogUtils.showConformAlertDialog1(context, TextMessages.ITEM_ALREADY_CARTED
, TextMessages.ITEM_ALREADY_CARTED_WARNING
, StringValues.NO, StringValues.YES, new DialogButtonClickListener() {
#Override
public void positiveButtonClick() {//adding new quantity to existing quantity.
//set view to call back method for fly to cart animation
if (getViewListener != null)
getViewListener.onGetView(itemViewHolder.copyStockImageView);
homeActivityViewModel.updateCartedItemWithExistingValues(stockId, stockQuantity
, CalcUtils.calculateTotalAmount(stockQuantity, stockAmount),
CalcUtils.calculateNetAmount(stockQuantity, stockAmount, stockDiscountAmount)
, null);
itemViewHolder.articleStockQuantityNumberPicker.setValue(1);//reset item quantity in ui.
}
#Override
public void negativeButtonClick() {
}
});
}
}
});
}
/**
* Here is the key method to apply the animation
*/
private void setAnimation(View viewToAnimate, int position) {
// If the bound view wasn't previously displayed on screen, it's animated
if (position > lastPosition) {
Animation animation = AnimationUtils.loadAnimation(context, R.anim.fall_down);
viewToAnimate.startAnimation(animation);
lastPosition = position;
}
}
/**
* register listener for get article image(copyStockImageView)
* for fly to cart animation.
* the view get back to the call back(categorizedArticleFragment)).
*
* #param getViewListener
*/
public void setGetViewListener(GetViewListener getViewListener) {
this.getViewListener = getViewListener;
}
#Override
public Filter getFilter() {
return itemFilter;
}
private class ItemFilter extends Filter {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
String filterString = constraint.toString().toLowerCase();
FilterResults results = new FilterResults();
final List<ArticleDetails> list = articleDetailsList;
int count = list.size();
final List<ArticleDetails> nlist = new ArrayList<ArticleDetails>(count);
ArticleDetails filterableString;
for (int i = 0; i < count; i++) {
filterableString = list.get(i);
if (filterableString.getArticleName().toLowerCase().contains(filterString)) {
nlist.add(filterableString);
}
}
results.values = nlist;
results.count = nlist.size();
return results;
}
#SuppressWarnings("unchecked")
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
articleDetailsList = (ArrayList<ArticleDetails>) results.values;
notifyDataSetChanged();
}
}
}
Fragments onCreate
#Override
public View onCreateView(#NonNull LayoutInflater layoutInflater, ViewGroup viewGroup,
Bundle savedInstanceState) {
context = viewGroup.getContext();
parentView = layoutInflater.inflate(R.layout.fragment_article, viewGroup, false);
homeActivityViewModel = getViewModel();
articleRecyclerView = parentView.findViewById(R.id.subcategory_rcylv);
Bundle bundle = getArguments();
Integer offset = bundle.getInt(TagNames.OFFSET, 0);
Integer numberOfRows = bundle.getInt(TagNames.NUMBER_OF_ROWS, FinalVariables.GETTING_DATA_COUNT_FROM_API);
filterTypeId = bundle.getInt(TagNames.FILTER_TYPE_ID, FilterTypes.SUGGESTED);
String filterText = bundle.getString(TagNames.FILTER_TEXT, "");
Integer categoryId = bundle.getInt(TagNames.CATEGORY_ID, 0);
initArticleRecyclerViewAdapter(articleDetailsList, filterText, categoryId, offset, numberOfRows);
return parentView;
}
Initializing Adapter
private void initArticleRecyclerViewAdapter(List<ArticleDetails> articleDetailsList
, String filterText, Integer categoryId, int offset, int numberOfRows) {
articleRecyclerViewAdapter = new ArticleRecyclerViewAdapter(context, this.articleDetailsList, homeActivityViewModel, this);
articleRecyclerViewAdapter.setLoadMoreListener(new ArticleRecyclerViewAdapter.OnLoadMoreListener() {
#Override
public void onLoadMore() {
articleRecyclerView.post(new Runnable() {
#Override
public void run() {
int index = ArticleFragment.this.articleDetailsList.size();
loadMore(filterText, categoryId, index, numberOfRows);
Log.e(TAG, "scrolling,index: " + index);
}
});
//Calling loadMore function in Runnable to fix the
// java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling error
}
});
articleRecyclerView.setHasFixedSize(true);
articleRecyclerView.setLayoutManager(new GridLayoutManager(context, 3));
articleRecyclerView.setAdapter(articleRecyclerViewAdapter);
load(filterText, categoryId, offset, numberOfRows);
}
Loading data
private void loadMore(String filterText, Integer categoryId, int offset, int numberOfRows) {
//add loading progress view
articleDetailsList.add(new ArticleDetails(true));
articleRecyclerViewAdapter.notifyItemInserted(articleDetailsList.size() - 1);
Integer storeId = FileUtils.getPreferenceInt(context
, Preferences.CUSTOMER_PREFERENCE, TagNames.STORE_ID);
Integer distributionChannelId = FileUtils.getPreferenceInt(context
, Preferences.CUSTOMER_PREFERENCE, TagNames.DISTRIBUTION_CHANNEL_ID);
homeActivityViewModel.getAllStockItemByCustomer(
DeviceUtils.getCredential(context, ServiceNames.GET_ALL_STOCK_ITEM_BY_CUSTOMER)
, storeId, distributionChannelId, filterTypeId, filterText, categoryId, offset, numberOfRows
, new ApiCallbackListener() {
#Override
public void onSuccess(Object object) {
try {
if (object != null) {
if (object instanceof GetAllStockItemByCustomerStatusReturn) {
//otp generation failed,
GetAllStockItemByCustomerStatusReturn getAllStockItemByCustomerStatusReturn
= (GetAllStockItemByCustomerStatusReturn) object;
showGetAllStockItemByCustomerErrorDialog(getAllStockItemByCustomerStatusReturn);
} else if (object instanceof List) {
List<GetAllStockItemByCustomerResponseData> getAllStockItemByCustomerResponseDataList
= (List<GetAllStockItemByCustomerResponseData>) object;
//remove loading view
articleDetailsList.remove(articleDetailsList.size() - 1);
List<ArticleDetails> result = new ArrayList<>();
result = homeActivityViewModel.getArticles(getAllStockItemByCustomerResponseDataList);
homeActivityViewModel.insertAllStockItemByCustomer(context,filterTypeId
, getAllStockItemByCustomerResponseDataList, null);
if (result.size() > 0) {
//add loaded data
articleDetailsList.addAll(result);
} else {//result size 0 means there is no more data available at server
articleRecyclerViewAdapter.setMoreDataAvailable(false);
//telling articleRecyclerViewAdapter to stop calling load more as no more server data available
//Toast.makeText(context, "No More Data Available", Toast.LENGTH_LONG).show();
}
articleRecyclerViewAdapter.notifyDataChanged();
//should call the custom method articleRecyclerViewAdapter.notifyDataChanged here to get the correct loading status
} else {
MDToast.makeText(context, FinalVariables.ToastMessages.SOMETHING_WENT_WRONG
, MDToast.LENGTH_SHORT, MDToast.TYPE_WARNING).show();
}
} else {
MDToast.makeText(context, FinalVariables.ToastMessages.SOMETHING_WENT_WRONG
, MDToast.LENGTH_SHORT, MDToast.TYPE_WARNING).show();
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
private void load(String filterText, Integer categoryId, int offset, int numberOfRows) {
Integer storeId = FileUtils.getPreferenceInt(context
, Preferences.CUSTOMER_PREFERENCE, TagNames.STORE_ID);
Integer distributionChannelId = FileUtils.getPreferenceInt(context
, Preferences.CUSTOMER_PREFERENCE, TagNames.DISTRIBUTION_CHANNEL_ID);
homeActivityViewModel.getAllStockItemByCustomer(
DeviceUtils.getCredential(context, ServiceNames.GET_ALL_STOCK_ITEM_BY_CUSTOMER)
, storeId, distributionChannelId, filterTypeId, filterText, categoryId, offset, numberOfRows
, new ApiCallbackListener() {
#Override
public void onSuccess(Object object) {
try {
if (object != null) {
if (object instanceof GetAllStockItemByCustomerStatusReturn) {
GetAllStockItemByCustomerStatusReturn getAllStockItemByCustomerStatusReturn
= (GetAllStockItemByCustomerStatusReturn) object;
showGetAllStockItemByCustomerErrorDialog(getAllStockItemByCustomerStatusReturn);
} else if (object instanceof List) {
List<GetAllStockItemByCustomerResponseData> getAllStockItemByCustomerResponseDataList
= (List<GetAllStockItemByCustomerResponseData>) object;
if (!getAllStockItemByCustomerResponseDataList.isEmpty()) {
if (offset == 0 && getAllStockItemByCustomerResponseDataList.get(0)
.getCategoryID() == StatusCodes.INVALID_CODE
&& filterTypeId == FilterTypes.SUGGESTED) {
//shows all article from store (filter type 0) if no article is mapped in suggested.
filterTypeId = FilterTypes.ALL;
load(filterText, categoryId, offset, numberOfRows);
}
}
articleDetailsList.addAll(homeActivityViewModel.getArticles(getAllStockItemByCustomerResponseDataList));
homeActivityViewModel.insertAllStockItemByCustomer(context,filterTypeId
, getAllStockItemByCustomerResponseDataList, null);
articleRecyclerViewAdapter.notifyDataChanged();
} else {
}
} else {
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
I am trying to create a timer for user-generated posts that is displayed on the posts. I am using Firestore to store all of the information about the posts.
The code I have does display the timer but when multiple posts are made by multiple users the timer flashes between the correct time and another count down which is unrelated as far as I can see. Here is what I have:
This is how I create each post.
post.setTimer(data.getStringExtra("timerDuration"));
Calendar cal = Calendar.getInstance();
cal.add(Calendar.SECOND, setEndDate());
Date endtime = cal.getTime();
post.setTimerEndDate(endtime);
addPhotoInfoToDatabase();
finish();
My Adapter for the Fragment:
adapter = new FirestoreRecyclerAdapter<Post, PostViewHolder>(options) {
#Override
protected void onBindViewHolder(#NonNull PostViewHolder postViewHolder, int position, #NonNull Post post) {
postViewHolder.setPost(post);
}
#NonNull
#Override
public PostViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.card_view_layout, parent, false);
return new PostViewHolder(view);
}
};
If there is more information you need please let me know. Thank you for any help.
Edit:
My app also contains a ViewPager and I have noticed that when I swipe away from the problem timer and swipe back the issue is fixed.
Update:
void setPost(final Post post) {
ImageView imageView1 = view.findViewById(R.id.firstImageCardView);
ImageView imageView2 = view.findViewById(R.id.secondImageCardView);
ImageView profilePic = view.findViewById(R.id.user_image);
TextView username = view.findViewById(R.id.user_name);
final TextView timer = view.findViewById(R.id.timer);
final Button chooseRight = view.findViewById(R.id.choose_right);
final Button chooseLeft = view.findViewById(R.id.choose_left);
final Button follow = view.findViewById(R.id.follow);
String displayName;
final String userId = currentUser.getUid();
final String postId = post.getUserId();
setProfilePicture(post);
final DocumentReference docRefUsers = db
.collection("users")
.document(currentUser.getUid());
final DocumentReference docRefFollowing = db
.collection("following")
.document(currentUser.getUid())
.collection("UserIsFollowing")
.document(post.getUserId());
final DocumentReference docRefPosts = db
.collection("posts")
.document(post.getUserId())
.collection("userPosts")
.document(post.getDate().toString());
if (userId.equals(postId)) {
displayName = "Me";
chooseLeft.setEnabled(false);
chooseRight.setEnabled(false);
follow.setText("");
follow.setEnabled(false);
} else if (post.getUsername() == null) {
displayName = "Anonymous";
} else {
displayName = post.getUsername();
}
if (post.getProfilePicture() != null) {
Picasso.get().load(post.getProfilePicture())
.transform(new CircleTransformActivity())
.fit()
.centerCrop()
.into(profilePic);
username.setText(displayName);
} else {
Picasso.get().load(R.drawable.blank_profile_pic)
.transform(new CircleTransformActivity())
.fit()
.into(profilePic);
}
/***********************************************************/
if(post.getTimerEndDate() != null) {
Date date = java.util.Calendar.getInstance().getTime();
long currentTime = date.getTime();
long endTime = post.getTimerEndDate().getTime();
long timeLeft = endTime - currentTime;
new CountDownTimer(timeLeft, 1000) {
#SuppressLint("DefaultLocale")
public void onTick(long millisUntilFinished) {
timer.setText(String.format("%02d:%02d:%02d",
(int) ((millisUntilFinished / (1000 * 60 * 60)) % 24),
(int) ((millisUntilFinished / (1000 * 60)) % 60),
(int) (millisUntilFinished / 1000) % 60));
//here you can have your logic to set text to edittext
}
public void onFinish() {
timer.setText("done!");
}
}.start();
}
Picasso.get()
.load(post.getImageUrl_1())
.fit()
.transform(new RoundedCornersTransformation(30, 30))
.into(imageView1);
Picasso.get()
.load(post.getImageUrl_2())
.fit()
.transform(new RoundedCornersTransformation(30, 30))
.into(imageView2);
if (!postId.equals(userId)) {
docRefFollowing.addSnapshotListener(new EventListener<DocumentSnapshot>() {
#Override
public void onEvent(#Nullable DocumentSnapshot documentSnapshot, #Nullable FirebaseFirestoreException e) {
if (documentSnapshot != null && documentSnapshot.exists()) {
follow.setText("Following");
follow.setEnabled(false);
}
}
});
}
follow.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
final Map<String, Object> following = new HashMap<>();
following.put("Exists", true);
docRefFollowing.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
docRefFollowing.set(following);
docRefUsers.update("following", FieldValue.increment(1));
}
}
});
db.collection("users")
.document(postId)
.update("followers", FieldValue.increment(1));
follow.setText("Following");
}
});
if (!postId.equals(userId)) {
docRefPosts.collection("voted")
.document(currentUser.getUid()).addSnapshotListener(new EventListener<DocumentSnapshot>() {
#Override
public void onEvent(#Nullable DocumentSnapshot documentSnapshot, #Nullable FirebaseFirestoreException e) {
if (documentSnapshot != null && documentSnapshot.exists()) {
whichVoted = documentSnapshot.get("votedFor").toString();
}
if (whichVoted != null) {
switch (whichVoted) {
case ("left"):
chooseLeft.setEnabled(false);
chooseRight.setEnabled(true);
break;
case ("right"):
chooseRight.setEnabled(false);
chooseLeft.setEnabled(true);
break;
}
} else {
chooseRight.setEnabled(true);
chooseLeft.setEnabled(true);
}
}
});
}
chooseLeft.setText(post.getLeftText());
chooseRight.setText(post.getRightText());
chooseLeft.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
chooseLeft.setEnabled(false);
chooseRight.setEnabled(true);
upLeft(docRefPosts, chooseLeft);
}
});
chooseRight.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
chooseRight.setEnabled(false);
chooseLeft.setEnabled(true);
upRight(docRefPosts, chooseRight);
}
});
}
Firstly, you should be aware that RecyclerView re-uses its viewholders each time you scroll past the last visible item on the screen.
Based on your implementation for every time a user scroll past the
last visible item or post on the screen, because it is inside a
RecyclerView the original first-item on the list and even other items
being scrolled past are recycled and the next item becomes the new
first-item but being populated with a different post-values as
expected.
But remember your code instantiates a new CountdownTimer for
every new post added to the list (where 50 posts equals 50
Countdowntimers and 50 posts does not necessarily mean 50 views because
some of the views would probably be recycled by the RecyclerView; you get the logic), hence it allows the
possibility for multiple CountdownTimer accessing same view which
causes the flash and will surely lead to a memory leak causing an
OutOfMemoryError when the user keeps scrolling continuously on a long
list.
Recommended Implementation
Create your CountDownTimer inside your ViewHolder Class
public static class PostViewHolder extends RecyclerView.ViewHolder {
...
CountDownTimer countdowntimer;
public FeedViewHolder(View itemView) {
...
}
}
Access the timer inside setPost method
void setPost(final Post post) {
...
long currentTime = date.getTime();
long endTime = post.getTimerEndDate().getTime();
long timeLeft = endTime - currentTime;
if (countdowntimer != null) {
//there is an existing timer so we cancel it to prevent duplicates
countdowntimer.cancel();
}
countdowntimer = new CountDownTimer(timeLeft, 1000) {
#SuppressLint("DefaultLocale")
public void onTick(long millisUntilFinished) {
timer.setText(String.format("%02d:%02d:%02d",
(int) ((millisUntilFinished / (1000 * 60 * 60)) % 24),
(int) ((millisUntilFinished / (1000 * 60)) % 60),
(int) (millisUntilFinished / 1000) % 60));
//here you can have your logic to set text to edittext
}
public void onFinish() {
timer.setText("done!");
}
}.start();
}
Each time you are creating a new timer when your items in the RecyclerView is getting recycled after scrolling. The timeLeft variable also changes each time you scroll the item and that initializes a new Timer which is the reason for the flashing I guess.
Hence I would like to recommend having an array in your adapter. This is for holding all of your CountDownTimers. In the constructor of your adapter, initialize that properly to have the correct values. Let us take a sample implementation like the following.
Declare two arrays like the following.
CountDownTimer[] timers = new CountDownTimer[data.size()];
Now in the constructor of your adapter do something like the following.
// Let us assume the list of Data is dataItems which is being passed to your adapter.
// Your constructor might take other parameters
public MyAdapter(ArrayList<Data> dataItems) {
for(int i = 0; i < dataItems.size(); i++) {
long timeLeft = getTimeLeftValue(dataItems.get(i));
timers[i] = new CountDownTimer(timeLeft, 1000) {
#SuppressLint("DefaultLocale")
public void onTick(long millisUntilFinished) {
timer.setText(String.format("%d:%d:%d",
(int) ((millisUntilFinished / (1000 * 60 * 60)) % 24),
(int) ((millisUntilFinished / (1000 * 60)) % 60),
(int) (millisUntilFinished / 1000) % 60));
//here you can have your logic to set text to edittext
}
public void onFinish() {
timer.setText("done!");
}
}.start();
}
}
private long getTimeLeftValue(Data post) {
Date date = java.util.Calendar.getInstance().getTime();
long currentTime = date.getTime();
long endTime = post.getTimerEndDate().getTime();
timeLeft = endTime - currentTime;
return timeLeft.
}
Now in your onBindViewHolder you need to set the timer as follows.
timerInstance = timers.get(position);
I am assuming that each of your items in the RecyclerView is having a timer instance which is showing the CountDownTimer.
You don't specify where exactly your logic is in your adapter.
The logic should be within onBindViewHolder(…). You can access the widgets using viewHolder.findViewById(...)
Also, CountDownTimer is its own thread, and editing a TextView or any widget can cause an exception. Wrap the timer.setText(…) call within runOnUiThread()
Check updates below.
I am making an app for a stair climbing challenge that tracks the date and number of steps taken (user input, not automatic). I have an ArrayList that stored objects containing the following three variables:
String date
Int steps
Instant timeStamp
The app has two input buttons, one for integer step input, and one for date selection. There is a simple method created to filter the visible list by the selected date and a couple of visual indicators of your daily progress vs. the daily goal for flights of stairs for the day.
App screenshot
I am using the Instant variable as a timestamp to try to get around the issue of the OnClickListener selecting the position of the item from the filtered list instead of the corresponding item in the unfiltered list. I do this by using the position reported from the OnClickListener to fetch the timeStamp variable from the associated item in the filtered ArrayList, then compare that timeStamp to the items in the unfiltered ArrayList and fetch the indexOf the matching item.
All filtered ArrayLists show properly in the RecyclerView when you select a date.
The problem comes in removing items. If I add items only to one date, then you can remove and add items as you'd expect.
App function without date change (gif)
If I add to one date, then another, while they display properly, the items will be removed from the correct position but in the date you first added items, regardless of whether that is the currently selected date or not.
App function with changing date (gif)
I feel like I'm missing something relatively simple here and my brain is just too saturated with this project to see it.
Main Activity:
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private ExampleAdapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
Date temp_curr_date = Calendar.getInstance().getTime();
SimpleDateFormat df = new SimpleDateFormat("dd-MMM-yyyy");
String sel_date = df.format(temp_curr_date);
String curr_date = df.format(temp_curr_date);
double daily_total;
int progress = 0;
double daily_goal = 7.5;
TextView textView1;
TextView textView2;
TextView textViewFlights;
ProgressBar pb;
List<ExampleItem> mExampleList;
List<ExampleItem> filteredList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ----- LOAD SAVED ARRAY LIST -----
loadData();
// ----- SET VARIABLES -----
daily_total = totalOutput(mExampleList, sel_date);
textView1 = findViewById(R.id.total);
textView1.setText(String.valueOf(daily_total));
textViewFlights = findViewById(R.id.flights);
pb = findViewById(R.id.progress_bar);
pb.setProgress(getProgress(mExampleList, sel_date), true);
// ----- BUILD RECYCLERVIEW -----
buildRecyclerView();
filter(sel_date);
// ----- ADD STEPS DIALOGUE -----
setAddStepButton();
// ----- CALENDAR DIALOGUE -----
setDateChangeButton();
}
public double totalOutput(List<ExampleItem> steps, String date) {
try{
int temp_total = 0;
double flight_total;
for (int a = 0; a < steps.size(); a++) {
if (date.equals(steps.get(a).getText1()))
temp_total += steps.get(a).getText2();
}
flight_total = round(temp_total / 16.0, 2);
return flight_total;
} catch (Exception e){
return 0.0;
}
}
public static double round(double value, int places) {
if (places < 0) throw new IllegalArgumentException();
BigDecimal bd = new BigDecimal(value);
bd = bd.setScale(places, RoundingMode.HALF_UP);
return bd.doubleValue();
}
public static int toInt(double value) {
BigDecimal bd = new BigDecimal(value);
bd = bd.setScale(0, RoundingMode.HALF_UP);
return bd.intValue();
}
public static Date getDate(int year, int month, int day) {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, year);
cal.set(Calendar.MONTH, month);
cal.set(Calendar.DAY_OF_MONTH, day);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
return cal.getTime();
}
private void saveData(){
SharedPreferences sharedPreferences = getSharedPreferences("shared preferences", MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
Gson gson = new Gson();
String json = gson.toJson(mExampleList);
editor.putString("task list", json);
editor.apply();
}
private void loadData(){
SharedPreferences sharedPreferences = getSharedPreferences("shared preferences", MODE_PRIVATE);
Gson gson = new Gson();
String json = sharedPreferences.getString("task list", null);
Type type = new TypeToken<ArrayList<ExampleItem>>() {}.getType();
mExampleList = gson.fromJson(json, type);
if (mExampleList == null){
mExampleList = new ArrayList<>();
}
}
private int getProgress(List<ExampleItem> steps, String date){
int daily_progress_int;
try{
int temp_progress = 0;
double flight_total;
for (int a = 0; a < steps.size(); a++) {
if (date.compareTo(steps.get(a).getText1()) == 0)
temp_progress += steps.get(a).getText2();
}
flight_total = round(temp_progress / 16.0, 2);
daily_progress_int = toInt((flight_total/daily_goal)*100);
return daily_progress_int;
} catch (Exception e){
return 0;
}
}
private void addProgress(double x, int prog){
int daily_progress_int = toInt((x/daily_goal)*100);
if (progress <= 100-daily_progress_int){
progress = progress + prog;
pb = findViewById(R.id.progress_bar);
pb.setProgress(daily_progress_int, true);
} else if (progress + daily_progress_int > 100){
pb = findViewById(R.id.progress_bar);
pb.setProgress(100, true);
}
}
private void removeProgress(double x, int prog){
int daily_progress_int = toInt((x/daily_goal)*100);
progress = progress - prog;
if (progress <= 100) {
pb = findViewById(R.id.progress_bar);
pb.setProgress(daily_progress_int, true);
} else {
pb = findViewById(R.id.progress_bar);
pb.setProgress(0, true);
}
}
public void addItem(String date, int steps, Instant ts){
mExampleList.add(new ExampleItem(date, steps, ts));
filter(sel_date);
}
public void removeItem(final int position){
final AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
View viewInflated = LayoutInflater.from(MainActivity.this).inflate(R.layout.confirm, (ViewGroup) findViewById(android.R.id.content), false);
builder.setCancelable(true);
builder.setView(viewInflated);
builder.setPositiveButton("Yup",
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
mExampleList.remove(position);
mAdapter.notifyDataSetChanged(position);
filter(sel_date);
daily_total = totalOutput(mExampleList, sel_date);
textView1 = findViewById(R.id.total);
textView1.setText(String.valueOf(daily_total));
removeProgress(daily_total,progress);
if (daily_total == 1.0){
textViewFlights.setText("flight");
} else {
textViewFlights.setText("flights");
}
saveData();
}
});
builder.setNegativeButton("Nope", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
public void buildRecyclerView(){
mRecyclerView = findViewById(R.id.recyclerView);
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(this);
mAdapter = new ExampleAdapter(mExampleList);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(mAdapter);
mAdapter.setOnItemClickListener(new ExampleAdapter.OnItemClickListener() {
#Override
public void onItemClick(int position) {
Instant test = filteredList.get(position).getTimeStamp();
for (ExampleItem item : mExampleList){
if (test.compareTo(item.getTimeStamp()) == 0){
removeItem(mExampleList.indexOf(item));
}
});
}
public void filter(String text){
filteredList = new ArrayList<>();
for (ExampleItem item : mExampleList){
if (item.getText1().toLowerCase().contains(text.toLowerCase())){
filteredList.add(item);
}
}
mAdapter.filterList(filteredList);
}
public void setAddStepButton(){
FloatingActionButton fab = findViewById(R.id.addSteps);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
View viewInflated = LayoutInflater.from(MainActivity.this).inflate(R.layout.add_steps, (ViewGroup) findViewById(android.R.id.content), false);
// Step input
final EditText input = viewInflated.findViewById(R.id.input);
builder.setView(viewInflated);
// OK Button
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
if (input.getText().length() != 0) {
try {
int in = Integer.parseInt(String.valueOf(input.getText()));
if (in > 0) {
Instant timeStamp = Instant.now();
addItem(sel_date, in, timeStamp);
dialog.dismiss();
} else {
dialog.cancel();
}
} catch (Exception e) {
dialog.cancel();
}
daily_total = totalOutput(mExampleList, sel_date);
textView1 = findViewById(R.id.total);
textView1.setText(String.valueOf(daily_total));
addProgress(daily_total, progress);
mAdapter.notifyDataSetChanged();
filter(sel_date);
if (daily_total == 1.0){
textViewFlights.setText("flight");
} else {
textViewFlights.setText("flights");
}
saveData();
} else{
dialog.cancel();
}
}
});
// Cancel Button
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
builder.show();
}
});
}
public void setDateChangeButton(){
FloatingActionButton fabcal = findViewById(R.id.calendarButton);
fabcal.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View view) {
LayoutInflater inflater = (LayoutInflater)getApplicationContext().getSystemService
(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout ll= (LinearLayout)inflater.inflate(R.layout.calendar, null, false);
CalendarView cv = (CalendarView) ll.getChildAt(0);
long milliseconds = 0;
try {
Date d = df.parse(sel_date);
milliseconds = d.getTime();
} catch (ParseException e) {
e.printStackTrace();
}
cv.setDate(milliseconds);
cv.setOnDateChangeListener(new CalendarView.OnDateChangeListener() {
#Override
public void onSelectedDayChange(
#NonNull CalendarView view,
int year,
int month,
int dayOfMonth)
{
Date temp_sel_date = getDate(year, month, dayOfMonth);
sel_date = df.format(temp_sel_date);
textView2 = findViewById(R.id.daily_total);
if (sel_date.equals(curr_date)){
textView2.setText("Today");
} else {
String dt_day = (String) DateFormat.format("dd", temp_sel_date);
String dt_month = (String) DateFormat.format("MMM", temp_sel_date);
textView2.setText(dt_month + " " + dt_day);
}
daily_total = totalOutput(mExampleList, sel_date);
textView1 = findViewById(R.id.total);
textView1.setText(String.valueOf(daily_total));
pb = findViewById(R.id.progress_bar);
pb.setProgress(getProgress(mExampleList, sel_date), true);
mAdapter.notifyDataSetChanged();
filter(sel_date);
}
});
new AlertDialog.Builder(MainActivity.this)
.setView(ll)
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
dialog.dismiss();
}
}
).show();
}
});
}
}
Adapter Class:
public class ExampleAdapter extends RecyclerView.Adapter<ExampleAdapter.ExampleViewHolder> {
private static List<ExampleItem> mExampleList;
private static List<ExampleItem> exampleListFull;
private OnItemClickListener mListener;
public interface OnItemClickListener{
void onItemClick(int position);
}
public void setOnItemClickListener(OnItemClickListener listener){
mListener = listener;
}
public static class ExampleViewHolder extends RecyclerView.ViewHolder {
public TextView mTextView1;
public ImageView mDeleteImage;
public ExampleViewHolder(View itemView, final OnItemClickListener listener) {
super(itemView);
mTextView1 = itemView.findViewById(R.id.textView);
mDeleteImage = itemView.findViewById(R.id.image_delete);
mDeleteImage.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (listener != null){
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION){
Instant test = mExampleList.get(position).getTimeStamp();
for (ExampleItem item : exampleListFull){
int compare = test.compareTo(item.getTimeStamp());
if (compare == 0){
int delIndex = exampleListFull.indexOf(item);
position = delIndex;
}
}
listener.onItemClick(position);
}
}
}
});
}
}
public ExampleAdapter(List<ExampleItem> exampleList){
this.mExampleList = exampleList;
exampleListFull = new ArrayList<>(exampleList);
}
#NonNull
#Override
public ExampleViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int i) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.example_item, parent, false);
ExampleViewHolder evh = new ExampleViewHolder(v, mListener);
return evh;
}
#Override
public void onBindViewHolder(#NonNull ExampleViewHolder holder, int position) {
ExampleItem currentItem = mExampleList.get(position);
if (currentItem.getText2() == 1.0){
holder.mTextView1.setText(currentItem.getText2() + " step");
} else {
holder.mTextView1.setText(currentItem.getText2() + " steps");
}
}
#Override
public int getItemCount() {
return mExampleList.size();
}
public void filterList(List<ExampleItem> filteredList){
mExampleList = filteredList;
notifyDataSetChanged();
}
}
If anyone out there has any ideas, I'd love to hear from you!
UPDATE: The included code now reflects the changes suggested by users and is fully functional.
You should use
mAdapter.notifyDataSetChanged();
Instead of
mAdapter.notifyItemRemoved(position);
For more details Visit
this.
I figured it out, for those interested. Comparing the timestamps was working fine, but I put it in the wrong part of the OnClickListener cycle. It needed to be placed in the MainActivity buildRecyclerView method in the setOnClickListener override. I've updated original code to reflect this change.
Thank you anyone that took the time to look at my post.
maybe it could be better to place the onClickListner in Your adpater-class..so You can control every single card if it should be clickable or not...or if You want to place any Ads between the cards
GGK
I am new in Espresso testing framework. Now I have a task to test some application which works with async backend. While the first activity starts some fragments appear only after they load. That can take several seconds, so the easiest way is just to wait 5-7 seconds. However using IdlingResource freezes main thread, so my backend data cannot load until waiting timeout is over.
That's how I use IdlingResource:
public static class ElapsedTimeIdlingResource implements IdlingResource {
private final long startTime;
private final long waitingTime;
private ResourceCallback resourceCallback;
ElapsedTimeIdlingResource(long waitingTime) {
this.startTime = System.currentTimeMillis();
this.waitingTime = waitingTime;
}
#Override
public String getName() {
return ElapsedTimeIdlingResource.class.getName() + ":" + waitingTime;
}
#Override
public boolean isIdleNow() {
long elapsed = System.currentTimeMillis() - startTime;
boolean idle = (elapsed >= waitingTime);
if (idle) resourceCallback.onTransitionToIdle();
return idle;
}
#Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
this.resourceCallback = resourceCallback;
}
}
That how I call it:
long waitingTime = 5000;
onView(withId(R.id.row_content)).check(matches(isDisplayed())).perform(click());
IdlingPolicies.setMasterPolicyTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);
IdlingPolicies.setIdlingResourceTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);
IdlingResource idlingResource = new ElapsedTimeIdlingResource(waitingTime);
IdlingRegistry.getInstance().register(idlingResource);
// .... do some tests
IdlingRegistry.getInstance().unregister(idlingResource);
How to delay test execution without blocking main thread?
So I have a splashscreen fragment that transactions to a the fragment I am testing after a delay. #Aarons answer worked for.
onView(isRoot()).perform(waitFor(5000))
Kotlin waitfor():
fun waitFor(delay: Long): ViewAction? {
return object : ViewAction {
override fun getConstraints(): Matcher<View> = isRoot()
override fun getDescription(): String = "wait for $delay milliseconds"
override fun perform(uiController: UiController, v: View?) {
uiController.loopMainThreadForAtLeast(delay)
}
}
}
You don't really need an IdlingResource if you just want to wait for an amount of time:
public static ViewAction waitFor(long delay) {
return new ViewAction() {
#Override public Matcher<View> getConstraints() {
return ViewMatchers.isRoot();
}
#Override public String getDescription() {
return "wait for " + delay + "milliseconds";
}
#Override public void perform(UiController uiController, View view) {
uiController.loopMainThreadForAtLeast(delay);
}
};
}
And use it:
onView(withId(R.id.row_content)).check(matches(isDisplayed())).perform(click());
onView(isRoot()).perform(waitFor(5000);
But if you know the view is going to appear after an amount of time, then you can use an IdlingResource for example:
public static ViewAction waitUntil(Matcher<View> matcher) {
return actionWithAssertions(new ViewAction() {
#Override public Matcher<View> getConstraints() {
return ViewMatchers.isAssignableFrom(View.class);
}
#Override public String getDescription() {
StringDescription description = new StringDescription();
matcher.describeTo(description);
return String.format("wait until: %s", description);
}
#Override public void perform(UiController uiController, View view) {
if (!matcher.matches(view)) {
LayoutChangeCallback callback = new LayoutChangeCallback(matcher);
try {
IdlingRegistry.getInstance().register(callback);
view.addOnLayoutChangeListener(callback);
uiController.loopMainThreadUntilIdle();
} finally {
view.removeOnLayoutChangeListener(callback);
IdlingRegistry.getInstance().unregister(callback);
}
}
}
});
}
private static class LayoutChangeCallback implements IdlingResource, View.OnLayoutChangeListener {
private Matcher<View> matcher;
private IdlingResource.ResourceCallback callback;
private boolean matched = false;
LayoutChangeCallback(Matcher<View> matcher) {
this.matcher = matcher;
}
#Override public String getName() {
return "Layout change callback";
}
#Override public boolean isIdleNow() {
return matched;
}
#Override public void registerIdleTransitionCallback(ResourceCallback callback) {
this.callback = callback;
}
#Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
matched = matcher.matches(v);
callback.onTransitionToIdle();
}
}
And use it for example:
onView(withId(R.id.row_content)).check(matches(isDisplayed())).perform(click());
onView(withId(R.id.main_content)).perform(waitUntil(isDisplayed()))
When am set Count down timer for single progress circle its work perfectly but when am using it for Listview my Progress Circle not work perfectly. Please tell me How to implement count down timer for listview in android.
private ListView lvHomeItems;
private List<HomeContent> homeListContent;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
lvHomeItems = (ListView) findViewById(R.id.lv_home_activity);
homeListContent = new ArrayList<>();
homeListContent.add(new HomeContent("3","22-10-2016","BDOI501",System.currentTimeMillis() + 10000));
homeListContent.add(new HomeContent("2","21-10-2016","BDOI502",System.currentTimeMillis() + 22000));
homeListContent.add(new HomeContent("4","20-10-2016","BDOI503",System.currentTimeMillis() + 34000));
homeListContent.add(new HomeContent("1","19-10-2016","BDOI504",System.currentTimeMillis() + 46000));
lvHomeItems.setAdapter(new CountdownAdapter(HomeActivity.this, homeListContent));
}
private class HomeContent {
long expirationTime;
String Date;
String Id;
String PrescriptionNo;
public HomeContent(String PrescriptionNo,String Date,String Id,long expirationTime) {
this.expirationTime = expirationTime;
this.PrescriptionNo = PrescriptionNo;
this.Date = Date;
this.Id = Id;
}
}
public class CountdownAdapter extends ArrayAdapter<HomeContent> {
private LayoutInflater layoutInflater;
private List<ViewHolder> homeListContent;
private Handler mHandler = new Handler();
private Runnable updateRemainingTimeRunnable = new Runnable() {
#Override
public void run() {
synchronized (homeListContent) {
long currentTime = System.currentTimeMillis();
for (ViewHolder holder : homeListContent) {
holder.updateTimeRemaining(currentTime);
}
}
}
};
public CountdownAdapter(Context context, List<HomeContent> objects) {
super(context, 0, objects);
layoutInflater = LayoutInflater.from(context);
homeListContent = new ArrayList<>();
startUpdateTimer();
}
private void startUpdateTimer() {
Timer tmr = new Timer();
tmr.schedule(new TimerTask() {
#Override
public void run() {
mHandler.post(updateRemainingTimeRunnable);
}
}, 1000, 1000);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
holder = new ViewHolder();
convertView = layoutInflater.inflate(R.layout.home_list_content, parent, false);
holder.tvCountDownTimer = (TextView) convertView.findViewById(R.id.tv_home_countTimeDown);
holder.tvDate = (TextView) convertView.findViewById(R.id.tv_home_date);
holder.tvOrderId = (TextView) convertView.findViewById(R.id.tv_home_order_id);
holder.tvPrescriptionNo = (TextView) convertView.findViewById(R.id.tv_priscription_no);
holder.tvViewDetails = (TextView) convertView.findViewById(R.id.tv_view);
holder.btnSelectBid = (Button) convertView.findViewById(R.id.btn_select_bid);
holder.mRingProgressBar = (RingProgressBar) convertView.findViewById(R.id.progress_bar_1);
//doSomeThing();
holder.tvViewDetails.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(HomeActivity.this,PrescriptionListActivity.class);
startActivity(intent);
}
});
holder.btnSelectBid.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// bidVenderList();
Intent intent = new Intent(HomeActivity.this,SelectBidActivity.class);
startActivity(intent);
}
});
convertView.setTag(holder);
synchronized (homeListContent) {
homeListContent.add(holder);
}
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.setData(getItem(position));
return convertView;
}
}
private class ViewHolder {
TextView tvViewDetails, tvCountDownTimer, tvDate, tvOrderId, tvPrescriptionNo;
Button btnSelectBid;
HomeContent mProduct;
RingProgressBar mRingProgressBar;
public void setData(HomeContent item) {
mProduct = item;
tvOrderId.setText("Order Id : " + item.Id);
tvDate.setText("Date : " + item.Date);
tvPrescriptionNo.setText("No of Prescription : " + item.PrescriptionNo);
updateTimeRemaining(System.currentTimeMillis());
}
public void updateTimeRemaining(long currentTime) {
final long timeDiff = mProduct.expirationTime - currentTime;
new Thread(new Runnable() {
public void run() {
while (mProgressStatus < 100) {
mProgressStatus += 1;
// Update the progress bar
mHandler.post(new Runnable() {
public void run() {
mRingProgressBar.setProgress(mProgressStatus);
mRingProgressBar.setMax(60);
if (timeDiff > 0) {
final int seconds = (int) (timeDiff / 1000) % 60;
final int minutes = (int) ((timeDiff / (1000 * 60)) % 60);
tvCountDownTimer.setText(minutes + " : " + seconds);
}
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
When am start my activity it not work perfectly... please tell me solution