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()))
Related
I have been trying to implement a debouncer for my application, I am trying to reduce requests to the server using this debouncer, eventually, I managed to implement the debouncer but, it seems to work only when I am using the debugger to debug the app.
This is how I implemented the debugger
public class NewDebouncedRunnable implements Runnable {
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private final Runnable operation;
private final long delayMillis;
private ScheduledFuture<?> scheduledFuture;
private long lastRunTime = -1;
private boolean isQueued = false;
private Context context;
public NewDebouncedRunnable(Context context,Runnable operation, String name, long delayMillis) {
this.operation = operation;
this.delayMillis = delayMillis;
this.context = context;
}
public void synchronized run() {
if(lastRunTime==-1){
Toasty.success(context,"LastRunTime reset",0).show();
}
long currentTime = getCurrentTimeMillis();
System.out.println(currentTime-lastRunTime);
if (shouldRunNow(currentTime)) {
// we've never called this before, call it now
System.out.println("Registered");
lastRunTime = currentTime;
operation.run();
} else {
System.out.println("Queued");
if(scheduledFuture!=null){
scheduledFuture.cancel(true);
}
schedule(this::scheduledRun, delayMillis);
}
}
private void scheduledRun() {
Log.d("SCHEDULED RUN","running scheduled task");
lastRunTime = getCurrentTimeMillis();
isQueued = false;
operation.run();
}
private boolean shouldRunNow(long currentTime) {
return currentTime-lastRunTime > delayMillis;
}
private void schedule(Runnable call, long delayMillis) {
scheduledFuture = scheduler.schedule(call, delayMillis, TimeUnit.MILLISECONDS);
}
long getCurrentTimeMillis() {
return System.currentTimeMillis();
}
}
This is my Debounced Runnable instance
NewDebouncedRunnable increaseOrderItemDebounce = new NewDebouncedRunnable(context,new Runnable() {
#Override
public void run() {
try{
Log.d("DEBOUNCE","Debounced run");
HelperClass.getInstance().updateOrderItem(context, item, item.getQuantity(), new HelperClass.ResponseListener<ResponseAddUpdateToCart>() {
#Override
public void onSuccess(ResponseAddUpdateToCart response) {
Toasty.success(context,"Cart Updated", Toast.LENGTH_SHORT).show();
}
#Override
public void onFailure(String error) {
}
});
}catch(Exception ex){
ex.printStackTrace();
}
}
},"",20000);
This is how I set my onClickListener for the button
holder.btnPlus.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
item.setQuantity(item.getQuantity()+1);
increaseOrderItemDebounce.run();
}
});
I should also note that the button is part of a recycler view, after making a successful request to the server, the adapter is notified that items have changed using adapter.notifyItemRangeChanged()like this
if (orderListAdapter != null && orderlistRview.getLayoutManager() != null) {
Parcelable recyclerViewState = orderlistRview.getLayoutManager().onSaveInstanceState();
orderlistRview.getLayoutManager().onRestoreInstanceState(recyclerViewState);
orderListAdapter.notifyItemRangeChanged(0,orderListAdapter.getItemCount());
}
The main issue I am facing here is that every time I click the button in regular mode the lastRunTime variable is reset, resulting in continuous requests to the server but when I am running the app using a debugger and setting breakpoints, it is working as it is intended to.
try this in Gradle.build
buildTypes {
release {
**debuggable true**
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
I can parse json data to my recyclerview. but when i meet json that has array data in it, my app forceclose when i try to get that. How to solve this? or anyone has similar sample code with this case?
Api:
https://v1.nocodeapi.com/fariz/fit/FunuohzdbtxBkVtC/aggregatesDatasets?dataTypeName=steps_count,heart_minutes,weight,activity_summary
Activity_Stepcounter.java
Call<nodeApi> call = jsonPlaceHolderApi.getNode();
call.enqueue(new Callback<nodeApi>() {
#Override
public void onResponse(Call<nodeApi> call, Response<nodeApi> response) {
progressBar.setVisibility(View.GONE);
nodeApi resource = response.body();
List<nodeApi.steps_count> dataList = resource.steps_count;
Adapter_meeting rv_adapter = new Adapter_meeting(dataList, Activity_Stepcounter.this);
rv.setLayoutManager(new LinearLayoutManager(Activity_Stepcounter.this));
rv.setAdapter(rv_adapter);
rv.hasFixedSize();
rv_adapter.notifyDataSetChanged();
}
#Override
public void onFailure(Call<nodeApi> call, Throwable t) {
call.cancel();
}
});
Adapter_meeting.java
public class Adapter_meeting extends RecyclerView.Adapter<Adapter_meeting.viewHolder> {
private final List<nodeApi.steps_count> mitem_meeting;
private final Context mContext;
public Adapter_meeting(List<nodeApi.steps_count> Item_meeting, Context mContext) {
this.mitem_meeting = Item_meeting;
this.mContext = mContext;
}
#NonNull
#Override
public viewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
final View view;
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.rv_step_counter, parent, false);
final viewHolder vHolder = new viewHolder(view);
return vHolder;
}
#Override
public void onBindViewHolder(#NonNull viewHolder holder, int position) {
holder.tv_namaEvent.setText(mitem_meeting.get(position).getValue());
holder.tv_lokasiEvent.setText(mitem_meeting.get(position).getStartTime());
holder.tv_waktuEvent.setText(mitem_meeting.get(position).getEndTime());
holder.tv_tanggalEvent.setText(mitem_meeting.get(position).getStartTimeMillis());
}
#Override
public int getItemCount() {
return mitem_meeting.size();
}
public class viewHolder extends RecyclerView.ViewHolder {
private final TextView tv_namaEvent;
private final TextView tv_lokasiEvent;
private final TextView tv_waktuEvent;
private final TextView tv_tanggalEvent;
public viewHolder(#NonNull View itemView) {
super(itemView);
tv_namaEvent = (TextView) itemView.findViewById(R.id.id_tv_namaEvent);
tv_lokasiEvent = (TextView) itemView.findViewById(R.id.id_tv_lokasiEvent);
tv_waktuEvent = (TextView) itemView.findViewById(R.id.id_tv_waktuEvent);
tv_tanggalEvent = (TextView) itemView.findViewById(R.id.id_tv_tanggalEvent);
}
}}
JsonPlaceHolderApi.java
public interface JsonPlaceHolderApi {
#GET("aggregatesDatasets?dataTypeName=steps_count,heart_minutes,weight,activity_summary")
Call<nodeApi> getNode();}
nodeApi.java
public class nodeApi {
#SerializedName("steps_count")
public List<steps_count> steps_count = null;
public class steps_count {
public Integer value;
public String startTimeMillis;
public String endTimeMillis;
public String startTime;
public String endTime;
public steps_count(Integer value, String startTimeMillis, String endTimeMillis, String startTime, String endTime) {
this.value = value;
this.startTimeMillis = startTimeMillis;
this.endTimeMillis = endTimeMillis;
this.startTime = startTime;
this.endTime = endTime;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
public String getStartTimeMillis() {
return startTimeMillis;
}
public void setStartTimeMillis(String startTimeMillis) {
this.startTimeMillis = startTimeMillis;
}
public String getEndTimeMillis() {
return endTimeMillis;
}
public void setEndTimeMillis(String endTimeMillis) {
this.endTimeMillis = endTimeMillis;
}
public String getStartTime() {
return startTime;
}
public void setStartTime(String startTime) {
this.startTime = startTime;
}
public String getEndTime() {
return endTime;
}
public void setEndTime(String endTime) {
this.endTime = endTime;
}
}
}
error
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.iot.testapp, PID: 12849
android.content.res.Resources$NotFoundException: String resource ID #0x19
at android.content.res.Resources.getText(Resources.java:335)
at android.widget.TextView.setText(TextView.java:4555)
at com.iot.testapp.adapter.Adapter_meeting.onBindViewHolder(Adapter_meeting.java:47)
at com.iot.testapp.adapter.Adapter_meeting.onBindViewHolder(Adapter_meeting.java:26)
On these lines:
holder.tv_tanggalEvent.setText(mitem_meeting.get(position).getStartTimeMillis());
holder.tv_lokasiEvent.setText(mitem_meeting.get(position).getStartTime());
holder.tv_waktuEvent.setText(mitem_meeting.get(position).getEndTime());
Compilator thinks, that you are passing String resource, but you are actually passing simple String. Just add +"" and it should start working.
holder.tv_tanggalEvent.setText(mitem_meeting.get(position).getStartTimeMillis()+"");
holder.tv_namaEvent.setText(mitem_meeting.get(position).getStartTime()+"");
holder.tv_waktuEvent.setText(mitem_meeting.get(position).getEndTime()+"");
You should pass text to textviews in form of String resource with placeholders, but it doesn't really make sense, when you don't pass there anything except the changing value.
holder.tv_namaEvent.setText(mitem_meeting.get(position).getValue());
You are trying to set Int.
Try:
String value = String.valueOf(mitem_meeting.get(position).getValue());
holder.tv_namaEvent.setText(value);
Please check all fields for null and read about name convention in Android :)
Also time in milis should be stored in Long type.
You can also read about Kotlin language, mvvn pattern etc.
Thanks, i got help from discord
I have to make jsonresponse first and then convert it to arraylist using nodeApi.java.
public class JsonResponse {
private nodeApi[] steps_count;
public nodeApi[] getStep_count() {
return steps_count;
}
public void setnodeApi(nodeApi[] step_count) {
this.steps_count = step_count;
}
}
Activity_Stepcounter.java
Call<JsonResponse> call = jsonPlaceHolderApi.getStep();
call.enqueue(new Callback<JsonResponse>() {
#Override
public void onResponse(Call<JsonResponse> call, Response<JsonResponse> response) {
progressBar.setVisibility(View.GONE);
JsonResponse jsonResponse = response.body();
listStep = new ArrayList<>(Arrays.asList(jsonResponse.getStep_count()));
Adapter_meeting rv_adapter = new Adapter_meeting(listStep, Activity_Stepcounter.this);
rv.setLayoutManager(new LinearLayoutManager(Activity_Stepcounter.this));
rv.setAdapter(rv_adapter);
rv.hasFixedSize();
rv_adapter.notifyDataSetChanged();
}
#Override
public void onFailure(Call<JsonResponse> call, Throwable t) {
call.cancel();
}
});
**there are some refactor/rename.
How to use Saved State module for ViewModel in Background Thread
For MutableLiveData we have the option to use setvalue and postvalue , where Postvalue can be used in background thread.
How ever How can we use BACKGROUND THREAD FOR Saved State module for ViewModel
here Is the code I am trying
public class CommonViewModel extends ViewModel {
private SavedStateHandle mState;
public CommonViewModel(SavedStateHandle savedStateHandle) {
mState = savedStateHandle;
}
private static final String NAME_KEY = "name";
private Executor mExecutor = Executors.newSingleThreadExecutor();
public LiveData<ArrayList<CommonOwn>> getCart() {
if (mState.getLiveData(NAME_KEY) == null) {
initCart();
}
return mState.getLiveData(NAME_KEY);
}
public void initCart() {
mState.set(NAME_KEY, new ArrayList<CommonOwn>());
}
public void addItemToCart(CommonOwn commonOwn) {
if (getCart().getValue() == null) {
initCart();
}
ArrayList<CommonOwn> cartItemList = new ArrayList<CommonOwn>(getCart().getValue());
if (cartItemList.contains(commonOwn)) {
int a = cartItemList.indexOf(commonOwn);
cartItemList.remove(a);
} else {
cartItemList.add(commonOwn);
}
// mState.set(NAME_KEY, cartItemList);
mExecutor.execute(new Runnable() {
#Override
public void run() {
mState.set(NAME_KEY, cartItemList);
}
});
}
}
when using background thread The following error occurs
java.lang.IllegalStateException: Cannot invoke setValue on a background thread
at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:487)
at androidx.lifecycle.LiveData.setValue(LiveData.java:306)
at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
at androidx.lifecycle.SavedStateHandle$SavingStateLiveData.setValue(SavedStateHandle.java:367)
at androidx.lifecycle.SavedStateHandle.set(SavedStateHandle.java:256)
at com.example.CommonViewModel$1.run(CommonViewModel.java:63)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
how can we solve this issue.
The following line can never be null:
mState.getLiveData(NAME_KEY) == null
Hope this illustrates the solution. You just rely on the MutableLiveData from SavedStateHandle:
public class CommonViewModel extends ViewModel {
private MutableLiveData<ArrayList<CommonOwn>> cart;
public CommonViewModel(SavedStateHandle savedStateHandle) {
cart = savedStateHandle.getLiveData(NAME_KEY);
}
private static final String NAME_KEY = "name";
private Executor mExecutor = Executors.newSingleThreadExecutor();
public MutableLiveData<ArrayList<CommonOwn>> getCart() {
return cart;
}
public void addItemToCart(CommonOwn commonOwn) {
ArrayList<CommonOwn> cartItemList;
if(cart.getValue() == null) {
cartItemList = new ArrayList<CommonOwn>();
} else {
cartItemList = cart.getValue();
}
if (cartItemList.contains(commonOwn)) {
int a = cartItemList.indexOf(commonOwn);
cartItemList.remove(a);
} else {
cartItemList.add(commonOwn);
}
// mState.set(NAME_KEY, cartItemList);
mExecutor.execute(new Runnable() {
#Override
public void run() {
cart.postValue(cartItemList);
}
});
}
}
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();
I have been trying for a while to implement a Game Thread to utilise a loop to implement logic. I posted a question here not long ago, I hope no one minds the follow up.
I have managed to scrape together this code from my research:
public class GameView extends SurfaceView implements SurfaceHolder.Callback
{
class GameThread extends Thread
{
//states
public static final int STATE_LOSE = 1;
public static final int STATE_PAUSE = 2;
public static final int STATE_READY = 3;
public static final int STATE_RUNNING = 4;
private Paint m_paint;
//canvas dimensions
private int m_canvasWidth;
private int m_canvasHeight;
private long m_lastTime;
private boolean m_run = false;
private int m_mode;
public ImageView ship;
RelativeLayout.LayoutParams shipParams;
// Handle to the surface manager
private SurfaceHolder m_surfaceHolder;
public GameThread(SurfaceHolder surfaceHolder, Context context, Handler handler)
{
m_surfaceHolder = surfaceHolder;
}
//Initialise the game
public void doStart()
{
synchronized (m_surfaceHolder)
{
resetGame();
m_lastTime = System.currentTimeMillis() + 100;
setState(STATE_RUNNING);
ship = (ImageView) findViewById(R.id.imageView1);
shipParams = (RelativeLayout.LayoutParams)ship.getLayoutParams();
}
}
public void pause()
{
synchronized (m_surfaceHolder)
{
if (m_mode == STATE_RUNNING)
setState(STATE_PAUSE);
}
}
#Override
public void run()
{
while (m_run)
{
Canvas c = null;
try
{
c = m_surfaceHolder.lockCanvas(null);
synchronized (m_surfaceHolder)
{
if (m_mode == STATE_RUNNING)
{
updateGame();
}
doDraw(c);
}
}
catch(Exception e){}
finally
{
if (c != null)
{
m_surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
public void setRunning(boolean b)
{
m_run = b;
}
public void setState(int mode)
{
synchronized (m_surfaceHolder)
{
setState(mode, null);
}
}
public void setState(int mode, CharSequence message)
{
synchronized (m_surfaceHolder)
{
m_mode = mode;
}
}
public void setPlayers(boolean onePlayer)
{
}
public void setSurfaceSize(int width, int height)
{
synchronized (m_surfaceHolder)
{
m_canvasWidth = width;
m_canvasHeight = height;
}
}
public void unpause()
{
synchronized (m_surfaceHolder)
{
m_lastTime = System.currentTimeMillis() + 100;
}
setState(STATE_RUNNING);
}
private void doDraw(Canvas canvas)
{
canvas.drawARGB(255, 0, 0, 0);
}
private void updateGame()
{
long now = System.currentTimeMillis();
if (m_lastTime > now)
return;
double elapsed = (now - m_lastTime) / 1000.0;
m_lastTime = now;
System.out.print("HELLO WORLD");
shipParams.topMargin++;
ship.setLayoutParams(shipParams);
}
private boolean collided(Rect rectangle)
{
return false;
}
public boolean foundWinner()
{
return false;
}
public void resetGame()
{
}
public void handleInput(MotionEvent event)
{
}
}
private Context m_context;
private GameThread m_thread;
private Handler m_handler;
public GameView(Context context, AttributeSet attrs)
{
super(context, attrs);
SurfaceHolder holder = getHolder();
holder.addCallback(this);
m_handler = new Handler() {
#Override
public void handleMessage(Message m) {
Bundle b = m.getData();
MotionEvent e = b.getParcelable("event");
m_thread.handleInput(e);
}
};
m_thread = new GameThread(holder, context, m_handler);
setFocusable(true);
};
public GameThread getThread()
{
return m_thread;
}
#Override
public void onWindowFocusChanged(boolean hasWindowFocus)
{
if (!hasWindowFocus)
m_thread.pause();
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
m_thread.setSurfaceSize(width, height);
}
public void surfaceCreated(SurfaceHolder holder)
{
if(m_thread.getState() == State.TERMINATED)
{
m_thread = new GameThread(getHolder(), m_context, m_handler);
m_thread.setRunning(true);
m_thread.start();
m_thread.doStart();
}
else
{
m_thread.setRunning(true);
m_thread.start();
}
}
public void surfaceDestroyed(SurfaceHolder holder)
{
boolean retry = true;
m_thread.setRunning(false);
while (retry)
{
try
{
m_thread.join();
retry = false;
}
catch (InterruptedException e)
{
}
}
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
return true;
}
}
I am fairly certain that my issue lies here and it is merely a logical one. Everything does seem fine to me, however and I am in need of assistance.
I have attempted to draw an image at line 47 and defined a movement to take place in the update method at line 153. I also have placed a print line for extra debug, but the line doesn't show.
I am stumped.
Any help would be great, thanks.
Here are my other codes, if neccessary:
MainActivity.java
GameSetup.java
game_setup.xml
edit: I should note that I'm not getting any kind of errors within the code, it merely doesn't respond
You are initializing m_run as false,then in the while cycle in the run() method you must have set to true. Change it to true and the thread will work normally.
set m_run to true in your doStart() procedure