Restart a Fragment - android

I'm having an issue restarting the fragment in my Activity. When the user completes a round, I need the fragment that is implemented by the activity to completely restart. The fragment has an if / else statement that checks to see if a round has already been played, and if so, it needs to reload the fragment with alternative values. I've been looking all over and can't seem to find much of anything on this, so any help is much appreciated!
public class BoardActivity extends Activity implements BoardFragment.OnFragmentInteractionListener {
public void gameTimer() {
Timer t = new Timer();
t.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
TextView tv = (TextView) findViewById(R.id.RoundTimer);
tv.setText(String.valueOf(minutes) + ":" + String.valueOf(seconds) + ":" + String.valueOf(milliseconds));
milliseconds -= 1;
if (milliseconds == 0) {
tv.setText(String.valueOf(minutes) + ":" + String.valueOf(seconds) + ":" + String.valueOf(milliseconds));
milliseconds = 1000;
seconds = seconds - 1;
}
if (seconds == 0) {
timerout = "Out of Time!";
tv.setText(String.valueOf(timerout));
}
if (timerout == "Out of Time!") {
Model.gameCounter = Model.gameCounter + 1;
nextRound();
}
}
});
}
}, 0, 1);
}
private void nextRound(){
//redirect to next round
if (Model.gameCounter <= 1 && Model.gameCounter < 4) {
Log.d(TAG,"new round");
Fragment frg = null;
frg = getFragmentManager().findFragmentByTag("BoardFragment");
final FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.detach(frg);
ft.attach(frg);
ft.commit();
} else {
//eventually add score screen
Log.d(TAG,"same round");
}
}

The documentation on attach and detach does say that the view hierarchy get destroyed and rebuilt. However, the state of the views is maintained, and so it should be expected to seem as if nothing has happened (which it sounds like what you are seeing). The docs say it is the same thing that happens when the fragment goes on the back stack.
Thus I think you have two options for "restarting" the fragment.
Option 1: Create a new fragment each time.
private void nextRound(){
//redirect to next round
if (Model.gameCounter <= 1 && Model.gameCounter < 4) {
Log.d(TAG,"new round");
Fragment frg = null;
frg = getFragmentManager().findFragmentByTag("BoardFragment");
Fragment newfrg = new BoardFragment();
final FragmentTransaction ft = getFragmentManager().beginTransaction();
if (frg == null) {
ft.add(R.id.board_container, newfrg, "BoardFragment");
} else {
ft.replace(R.id.board_container, newfrg, "BoardFragment");
}
ft.commit();
} else {
//eventually add score screen
Log.d(TAG,"same round");
}
}
Option 2: Write a method in BoardFragment to reset the board for the next round, and call that when you are restarting.
private void nextRound(){
//redirect to next round
if (Model.gameCounter <= 1 && Model.gameCounter < 4) {
Log.d(TAG,"new round");
BoardFragment frg = (BoardFragment)
getFragmentManager().findFragmentByTag("BoardFragment");
frg.restart();
} else {
//eventually add score screen
Log.d(TAG,"same round");
}
}

Related

Fragment still exist after remove in SDK < 24

I'm facing the problem about removing Fragment in SDK < 24.
removeFragment()
FragmentTransaction frgTrans = fragmentMng.beginTransaction();
MyFragment myFrg = (MyFragment) fragmentMng.findFragmentByTag(TAG_MY_FRAGMENT);
frgTrans.remove(myFrg).commit();
getFragment()
MyFragment myFrg = (MyFragment) fragmentMng.findFragmentByTag(TAG_MY_FRAGMENT);
if (myFrg == null ) {
// Do sth
}
// But I checked that myFrg is NOT NULL ???
Furthermore, this problem only happened in SDK < 24 ( Android 5,6 ).
What's difference things between Android SDK < 24 and 24 above ?
I also try to call commitNow() for execute synchronously but it's same problem.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
frgTrans.commit();
fragmentMng.popBackStack();
} else {
frgTrans.commitNow();
}
Anyone here has same problem ?
Update:
I also check Fragment hashcode I get before add and before remove. It's same so I can affirm that it's existing...
05-12 11:34:38.705 3916-3916/myapp.test E/FragmentControllerTest: hashcode before remove: 136290746
05-12 11:34:39.856 3916-3916/myapp.test E/FragmentControllerTest: hashcode before add: 136290746
Update code
Calling follow:
GotoActivity 1: AddMyFragment()
GotoActivity 2: (destroy Activity1 ) removeMyFragment()
BackToActivity1: AddMyFragment() (onResume)
Code:
private void addMyFragment() {
MyFragment myFrg = (MyFragment) mActivity.getSupportFragmentManager().findFragmentByTag(TAG_MY_FRAGMENT);
if (myFrg == null) {
try {
myFrg = new MyFragment();
FragmentTransaction frgTrans = mActivity.getSupportFragmentManager().beginTransaction();
frgTrans.add(R.id.my_fragment, myFrg, TAG_MY_FRAGMENT);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
frgTrans.commit();
} else {
frgTrans.commitNow();
}
} catch (Exception e) {}
} else {
Log.e(TAG, "hash code after remove: " + myFrg.hashCode());
}
}
private void removeMyFragment() {
MyFragment myFrg = (MyFragment) mActivity.getSupportFragmentManager().findFragmentByTag(TAG_MY_FRAGMENT);
if (myFrg != null) {
Log.e(TAG, "hash code after add: " + myFrg.hashCode());
try {
FragmentTransaction frgTrans = mActivity.getSupportFragmentManager().beginTransaction();
frgTrans.remove(myFrg);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
frgTrans.commit();
mActivity.getSupportFragmentManager().popBackStack();
} else {
frgTrans.commitNow();
}
} catch (Exception e) {}
}
}

Android TV: PlaybackControlsRow action long press

I'm trying to implement a fast-forward and rewind actions using PlaybackControlsRow using Leanback library for Android TV, however I can't find any method to detect a long press on these buttons. My current implementation is simple, only does seeking for 10 seconds on one click:
private void setupRows() {
final ClassPresenterSelector ps = new ClassPresenterSelector();
final PlaybackControlsRowPresenter playbackControlsRowPresenter =
new PlaybackControlsRowPresenter(new DescriptionPresenter());
playbackControlsRowPresenter.setOnActionClickedListener(action -> {
if (action.getId() == playPauseAction.getId()) {
togglePlayback(playPauseAction.getIndex() == PlayPauseAction.PLAY);
} else if (action.getId() == fastForwardAction.getId()) {
fastForward();
return;
} else if (action.getId() == rewindAction.getId()) {
rewind();
return;
}
if (action instanceof PlaybackControlsRow.MultiAction) {
((PlaybackControlsRow.MultiAction) action).nextIndex();
notifyChanged(action);
}
});
ps.addClassPresenter(PlaybackControlsRow.class, playbackControlsRowPresenter);
ps.addClassPresenter(ListRow.class, new ListRowPresenter());
rowsAdapter = new ArrayObjectAdapter(ps);
updatePlaybackControlsRow();
setAdapter(rowsAdapter);
}
private void fastForward() {
((PlaybackActivity) getActivity()).onFragmentFastForward();
final int currentTime = ((PlaybackActivity) getActivity()).getPosition();
playbackControlsRow.setCurrentTime(currentTime);
}
private void rewind() {
((PlaybackActivity) getActivity()).onFragmentRewind();
final int currentTime = ((PlaybackActivity) getActivity()).getPosition();
playbackControlsRow.setCurrentTime(currentTime);
}
In PlaybackActivity:
public void onFragmentFastForward() {
// Fast forward 10 seconds.
videoView.seekTo(videoView.getCurrentPosition() + (10 * 1000));
}
public void onFragmentRewind() {
videoView.seekTo(videoView.getCurrentPosition() - (10 * 1000));
}
Is it possible to implement fast-forward and rewind on long press of actions, like key-up/key-down events on the action buttons?
After looking for other solutions, seems that PlaybackControlsRowPresenter is designed in the way that it should have no long presses, but instead increasing/reducing speed by the number of clicks on fast-forward/rewind buttons. It can be clearly seen from internal constructor implementation of PlaybackControlsRowPresenter.FastForwardAction and PlaybackControlsRowPresenter.RewindAction classes:
/**
* Constructor
* #param context Context used for loading resources.
* #param numSpeeds Number of supported fast forward speeds.
*/
public FastForwardAction(Context context, int numSpeeds) {
So, as the result, my solution for now is increasing/reducing a speed by each click on the fast-forward/rewind buttons (which is done on UI by default). After that, when I click on play - it just resumes video from the seeked point.
UPDATE (updated parts of code):
private void setupRows() {
final ClassPresenterSelector ps = new ClassPresenterSelector();
final PlaybackControlsRowPresenter playbackControlsRowPresenter =
new PlaybackControlsRowPresenter(new DescriptionPresenter());
playbackControlsRowPresenter.setOnActionClickedListener(action -> {
if (action.getId() == playPauseAction.getId()) {
togglePlayback(playPauseAction.getIndex() == PlayPauseAction.PLAY);
((PlaybackControlsRow.MultiAction) action).nextIndex();
notifyChanged(action);
} else if (action.getId() == fastForwardAction.getId()) {
if (currentSpeed <= MAX_SPEED) {
currentSpeed++;
showTogglePlayback(false, true);
if (currentSpeed < 0) {
prevRewind();
} else if (currentSpeed > 0) {
nextFastForward();
} else {
currentSpeed++;
prevRewind();
nextFastForward();
}
((PlaybackActivity) getActivity()).seek(currentSpeed);
}
} else if (action.getId() == rewindAction.getId()) {
if (currentSpeed >= MIN_SPEED) {
currentSpeed--;
showTogglePlayback(false, true);
if (currentSpeed > 0) {
prevFastForward();
} else if (currentSpeed < 0) {
nextRewind();
} else {
currentSpeed--;
prevFastForward();
nextRewind();
}
((PlaybackActivity) getActivity()).seek(currentSpeed);
}
} else if (action.getId() == R.id.lb_control_picture_in_picture &&
PlaybackActivity.supportsPictureInPicture(getActivity())) {
getActivity().enterPictureInPictureMode();
}
});
ps.addClassPresenter(PlaybackControlsRow.class, playbackControlsRowPresenter);
ps.addClassPresenter(ListRow.class, new ListRowPresenter());
rowsAdapter = new ArrayObjectAdapter(ps);
updatePlaybackControlsRow();
setAdapter(rowsAdapter);
}
private void prevFastForward() {
fastForwardAction.setIndex(fastForwardAction.getIndex() - 1);
notifyChanged(fastForwardAction);
}
private void nextFastForward() {
fastForwardAction.nextIndex();
notifyChanged(fastForwardAction);
}
private void prevRewind() {
rewindAction.setIndex(rewindAction.getIndex() - 1);
notifyChanged(rewindAction);
}
private void nextRewind() {
rewindAction.nextIndex();
notifyChanged(rewindAction);
}

Cannot close turnbased multiplayer match with Google Play Games

I have a problem closing my game after the last player close the last match. My turn scheme is:
Player A
Player B
Player B
Player A
Player A
Player B
The game works well but in turn "6" when player B try to close the match, player A always see the matsh as "my turn" and not as "completed"
here there is the code thar rules turns and game ended:
#Override
public void onGameEnd(final NMXGameData updatedData) {
super.onGameEnd(updatedData);
if (updatedData.getMatchNumber() == NMXGameConfig.MATCHES) {
boolean iWin = updatedData.getResultPoints()[1] > updatedData.getResultPoints()[0];
boolean tile = updatedData.getResultPoints()[1] == updatedData.getResultPoints()[0];
ParticipantResult opponentParticipantResult;
ParticipantResult myParticipantResult;
if (tile) {
opponentParticipantResult = new ParticipantResult(getOpponentId(), ParticipantResult.MATCH_RESULT_TIE, 1);
myParticipantResult = new ParticipantResult(getCurrentPlayerId(), ParticipantResult.MATCH_RESULT_TIE, 1);
} else {
if (iWin) {
opponentParticipantResult = new ParticipantResult(getOpponentId(), ParticipantResult.MATCH_RESULT_LOSS, 2);
myParticipantResult = new ParticipantResult(getCurrentPlayerId(), ParticipantResult.MATCH_RESULT_WIN, 1);
} else {
opponentParticipantResult = new ParticipantResult(getOpponentId(), ParticipantResult.MATCH_RESULT_WIN, 1);
myParticipantResult = new ParticipantResult(getCurrentPlayerId(), ParticipantResult.MATCH_RESULT_LOSS, 2);
}
}
ArrayList<ParticipantResult> participantResultArrayList = new ArrayList<>();
participantResultArrayList.add(opponentParticipantResult);
participantResultArrayList.add(myParticipantResult);
Games.TurnBasedMultiplayer.finishMatch(getApiClient(), match.getMatchId(), new Gson().toJson(updatedData).getBytes(), opponentParticipantResult, myParticipantResult).setResultCallback(new ResultCallback<TurnBasedMultiplayer.UpdateMatchResult>() {
#Override
public void onResult(TurnBasedMultiplayer.UpdateMatchResult updateMatchResult) {
finish();
}
});
} else if (updatedData.getMatchNumber() < NMXGameConfig.MATCHES) {
if (getNextPlayerIndex(updatedData.getMatchNumber()) != getNextPlayerIndex(updatedData.getMatchNumber() - 1)) {
Games.TurnBasedMultiplayer.takeTurn(getApiClient(), match.getMatchId(), new Gson().toJson(updatedData).getBytes(), getNextParticipantId());
} else {
Games.TurnBasedMultiplayer.takeTurn(getApiClient(), match.getMatchId(), new Gson().toJson(updatedData).getBytes(), getCurrentPlayerId());
startActivity(startNewOnlineGameIntent(this, updatedData, match.getMatchId()));
}
finish();
}
}
private String getCurrentPlayerId() {
return match.getParticipantId(Games.Players.getCurrentPlayerId(getApiClient()));
}
private String getOpponentId() {
for (String id : match.getParticipantIds()) {
if (!id.equals(getCurrentPlayerId())) {
return id;
}
}
return null;
}
private int getNextPlayerIndex(int nextRoundIndex) {
nextRoundIndex = nextRoundIndex + 1;
return (nextRoundIndex / 2) % 2;
}
I finally figured it out.
I don't know if that is the desired behavior but when in round 6 player_B calls:
Games.TurnBasedMultiplayer.finishMatch(getApiClient(), match.getMatchId(), new Gson().toJson(updatedData).getBytes(), opponentParticipantResult, myParticipantResult).setResultCallback(new ResultCallback<TurnBasedMultiplayer.UpdateMatchResult>() {
#Override
public void onResult(TurnBasedMultiplayer.UpdateMatchResult updateMatchResult) {
finish();
}
});
The turn goes to player_A that see that match as "my turn". At this point player A must call Games.TurnBasedMultiplayer.finishMatch(getApiClient(), match.getMatchId()) (without playing a real game) and the game is completed for both players

Only One fragment is displayed

I have a spinner item.When I click on position 2. I want the following code to execute.
if (pos == 2) {
Log.e("TEST", "YOU CLICKED" + pos);
for (int i = 0; i < 2; i++) {
Log.e("TEST INSIDE FOR", "YOU CLICKED" + pos);
if (i % 2 == 0) {
// showFragOne();
showFragOne();
Log.e("TEST INSIDE FOR " + i, "YOU CLICKED" + pos);
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else if (i %2 == 1) {
// showFragTwo();
showFragTwo();
Log.e("TEST INSIDE FOR " + i, "YOU CLICKED" + pos);
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Toast.makeText(mCoreContext,"One Fragment added from feature app",Toast.LENGTH_LONG).show();
}
What i want this code to do is change the Frame layout in 20s. But only the second fragment I am getting at last. Here showFragTwo() function:-
private void showFragTwo() {
// TODO Auto-generated method stub
FragmentTransaction ft =
.getFragmentManager().beginTransaction();
if (fragtwo.isAdded()) {
if (fragtwo.isHidden())
ft.show(fragtwo);
else
ft.hide(fragtwo);
ft.commit();
} else {
ft.replace(com.example.coreapp.R.id.container, fragtwo,
"ADDFRAGTWO").commit();
ft.show(fragtwo);
}
}
Here showFragOne() function:-
private void showFragOne() {
// TODO Auto-generated method stub
FragmentTransaction ft =
.getFragmentManager().beginTransaction();
if (frag.isAdded()) {
if (frag.isHidden())
ft.show(frag);
else
ft.hide(frag);
ft.commit();
} else {
ft.replace(com.example.coreapp.R.id.container, frag, "ADDFRAG")
.commit();
ft.show(frag);
}
}
where i initialize frag and fragtwo at top to following to fragment class :-
frag = new AddFragmentOne();
fragtwo = new AddFragmentTwo();
I get the following as logcat o/p.
**02-28 04:56:34.250: E/TEST INSIDE FOR: YOU CLICKED2
02-28 04:56:34.290: E/TEST INSIDE FOR 0: YOU CLICKED2
02-28 04:56:54.350: E/TEST INSIDE FOR: YOU CLICKED2
02-28 04:56:54.350: E/TEST INSIDE FOR 1: YOU CLICKED2**
I am getting the logs message in o/p BUT WHY THE FRAGMENT IS NOT GETTING DISPLAYED?
ONLY THE SECOND FRAGMENT I AM GETTING
If i comment second if statemnt I AM GETTING FIRST FRAGMENT
Can anybody tell me where i am getting wrong?
This happens because you make the Thread sleep 20 seconds and its likely the Ui Thread.
You should replace this Thread.sleep() statement by a Timer that shows the appropriate fragment :
if(pos == 2) {
showFragOne();
new Timer().schedule(new TimerTask() {
#Override
public void run() {
showFragTwo();
}
}, 20000);
}
To complete the answer based on commentaries : I suggest you make one method
/** This method shows the fragment whose 'number' is {#code fragmentNumber}. */
public void showFrag(int fragmentNumber) {
Fragment f = fragments.get(fragmentNumber); // fragments can be a List for example
// Then use the code you already have to show fragment 'f'
}
Then, you can modify the preceding code like this :
if(pos == 2) {
final AtomicInteger count = new AtomicInteger(0); // Because use inside TimerTask requires a final variable
new Timer().scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
if(count.get() < MAX_FRAG_INDEX)
showFrag(count.getAndIncrement);
else
cancel();
}
}, 0, 2000);
}

CountDownTime inside Listview

I am developing one android application and on one screen i have Listview with countdown timer on each row. I am using List Adapter. I tried Google and other reference but not getting actual output. I have seconds returned from web-service, I am converting seconds to hh:mm:ss format and started countdown timer using CountdownTimer class. but when i am scrolling Listview it changes behavior, I know this is the common problem for android Listview but i want to display countdown timer on each row with complete moving time second by second.
Please help me friend, Thanks in advance.
meh from this thread here gave a good advice.
From within the CountDownTimer, don't update the textviews in the listview-item directly, but update some String in the data-objects from the adapter. Afterwards let another separate CountDownTimer call adapter.notifyDataSetChanged() to let the listitem-views update themselves. I call this one adapterNotifier in code.
The adapterNotifier alone should call adapter.notifyDataSetChanged() to avoid the additional overhead if every other single CountDownTimer would itself call adatper.notifyDataSetChanged().
In the getView()-method from the adapter, don't do anything fancy, only read the pre-computed strings from the data-object and update the textviews as usual.
The main drawback to this approach is, that you update the whole of every listitem, which could be overkill if you have e.g. network-loaded images in the listitems. I will try to optimize this further.
Here is some code
public Adapter(Context context, ListData dataList) {
super(context, dataList);
this.dataList = dataList;
initTimerData(dataList);
}
private void initTimerData(ListData dataList) {
long longestTimerInterval = 0;
for (final Data data : dataList) {
if (data.show_timer) {
if (data.end_time == null) {
data.show_timer = false;
continue;
}
long endTimeMillis = 0;
try {
endTimeMillis = dataDateTimeProcessor.parse(data.end_time).getTime();
} catch (ParseException e) {
Log.e(DataDetailFragment.class.getSimpleName(), "Error parsing data-time: ", e);
}
// TODO use the server-time for reliability
long nowTime = System.currentTimeMillis();
long timeInterval = endTimeMillis - nowTime;
// if the data is over or more than 24 hours away, don't display the timer
if (nowTime >= endTimeMillis ||
timeInterval > 24 * 3600 * 1000) {
data.show_timer = false;
continue;
}
// save the longest interval for an extra adapter-notifier timer
if (timeInterval > longestTimerInterval)
longestTimerInterval = timeInterval;
// this listener holds the references to the currently visible data-timerviews
DataCountDownTimerListener timerListener = new DataCountDownTimerListener() {
#Override
public void onTick(long millisUntilFinished) {
int seconds = (int) ((millisUntilFinished / 1000) % 60);
int minutes = (int) ((millisUntilFinished / (60000)) % 60);
int hours = (int) ((millisUntilFinished / (3600000)) % 24);
data.timerSeconds = String.format("%02d", seconds);
data.timerMinutes = String.format("%02d", minutes);
data.timerHours = String.format("%02d", hours);
}
#Override
public void onFinish() {
data.timerSeconds = "00";
data.timerMinutes = "00";
data.timerHours = "00";
}
};
if (data.endTimer != null) {
Utils.stopDataTimer(data);
}
data.endTimer = new DataCountDownTimer(timeInterval, 1000);
data.endTimer.setDataCountDownTimerListener(timerListener);
data.endTimer.start();
} else {
data.show_timer = false;
}
}
initAdapterNotifier(longestTimerInterval);
}
private void initAdapterNotifier(long longestTimerInterval) {
// no timers here, go away
if (longestTimerInterval == 0)
return;
adapterNotifier = new DataCountDownTimer(longestTimerInterval, 1000);
DataCountDownTimerListener adapterNotifierListener = new DataCountDownTimerListener() {
#Override
public void onTick(long millisUntilFinished) {
// TODO evaluate performance
// update the adapter-dataset for timer-refreshes
notifyDataSetChanged();
}
#Override
public void onFinish() {
// last timer finished, nothing to do here
}
};
adapterNotifier.setDataCountDownTimerListener(adapterNotifierListener );
adapterNotifier.start();
}
public void stopDataTimers() {
if (adapterNotifier != null)
adapterNotifier.cancel();
for (Data data : dataList) {
if (data.endTimer != null) {
Utils.stopDataTimer(data);
}
}
}
you can create a class that will handle your timer in each row. I mean a reusable class. you already created a class to make your timer then utilize it.In that class, you should be able to monitor your listview item so that the timer will continue to run. you can reference the id of that listitem.
hope it gives you some hint

Categories

Resources