Skip to content

Commit

Permalink
Improved restoration mechanism and added new callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
ernestoyaquello committed Jan 13, 2022
1 parent 4ebe8d0 commit 4ca0ee6
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 110 deletions.
36 changes: 0 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,42 +212,6 @@ This method will get called when the user clicks on the optional cancellation bu

Right before calling this method, the form disables the navigation between steps, as well as all the buttons. To revert the form to normal (for example, because the user decides not to cancel it), it is necessary to call `verticalStepperForm.cancelFormCompletionOrCancellationAttempt()`.

### 5. Handle Configuration Changes
To restore your form after configuration changes, such as screen rotation, you must save and restore the data of all your steps like this:

```java
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putString("user_name", userNameStep.getStepData());
savedInstanceState.putString("user_email", userEmailStep.getStepData());
savedInstanceState.putInt("user_age", userAgeStep.getStepData());

// IMPORTANT: The call to the super method must be here at the end.
super.onSaveInstanceState(savedInstanceState);
}

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
if(savedInstanceState.containsKey("user_name")) {
String userName = savedInstanceState.getString("user_name");
userNameStep.restoreStepData(userName);
}

if(savedInstanceState.containsKey("user_email")) {
String userEmail = savedInstanceState.getString("user_email");
userEmailStep.restoreStepData(userEmail);
}

if(savedInstanceState.containsKey("user_age")) {
int userAge = savedInstanceState.getInt("user_age");
userAgeStep.restoreStepData(userAge);
}

// IMPORTANT: The call to the super method must be here at the end.
super.onRestoreInstanceState(savedInstanceState);
}
```

## Further Details
Check out the [sample application code](https://github.com/ernestoyaquello/VerticalStepperForm/tree/master/app/src/main/java/verticalstepperform/ernestoyaquello/com/verticalstepperform) to see a more complete example of how this library can be used to create vertical stepper forms.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment;

import ernestoyaquello.com.verticalstepperform.Step;
import ernestoyaquello.com.verticalstepperform.listener.StepperFormListener;
import verticalstepperform.ernestoyaquello.com.verticalstepperform.databinding.FragmentNewAlarmBinding;
import verticalstepperform.ernestoyaquello.com.verticalstepperform.form.steps.AlarmDaysStep;
Expand All @@ -33,12 +34,6 @@ public class NewAlarmFormFragment extends Fragment implements StepperFormListene

public static final String ALARM_DATA_SERIALIZED_KEY = "newAlarmData";

public static final String STATE_TITLE = "title";
public static final String STATE_DESCRIPTION = "description";
public static final String STATE_TIME_HOUR = "time_hour";
public static final String STATE_TIME_MINUTES = "time_minutes";
public static final String STATE_WEEK_DAYS = "week_days";

private ProgressDialog progressDialog;
private FragmentNewAlarmBinding binding;

Expand Down Expand Up @@ -104,6 +99,16 @@ public void onCancelledForm() {
showCloseConfirmationDialog();
}

@Override
public void onStepAdded(int index, Step<?> addedStep) {
// Nothing to do here
}

@Override
public void onStepRemoved(int index) {
// Nothing to do here
}

private Thread saveData() {
// Fake data saving effect
Thread thread = new Thread(() -> {
Expand Down Expand Up @@ -192,48 +197,6 @@ public void onStop() {
dismissDialogIfNecessary();
}

@Override
public void onSaveInstanceState(@NonNull Bundle outState) {

outState.putString(STATE_TITLE, nameStep.getStepData());
outState.putString(STATE_DESCRIPTION, descriptionStep.getStepData());
outState.putInt(STATE_TIME_HOUR, timeStep.getStepData().hour);
outState.putInt(STATE_TIME_MINUTES, timeStep.getStepData().minutes);
outState.putBooleanArray(STATE_WEEK_DAYS, daysStep.getStepData());

super.onSaveInstanceState(outState);
}

@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {

if(savedInstanceState != null && savedInstanceState.containsKey(STATE_TITLE)) {
String title = savedInstanceState.getString(STATE_TITLE);
nameStep.restoreStepData(title);
}

if(savedInstanceState != null && savedInstanceState.containsKey(STATE_DESCRIPTION)) {
String description = savedInstanceState.getString(STATE_DESCRIPTION);
descriptionStep.restoreStepData(description);
}

if(savedInstanceState != null && savedInstanceState.containsKey(STATE_TIME_HOUR)
&& savedInstanceState.containsKey(STATE_TIME_MINUTES)) {
int hour = savedInstanceState.getInt(STATE_TIME_HOUR);
int minutes = savedInstanceState.getInt(STATE_TIME_MINUTES);
AlarmTimeStep.TimeHolder time = new AlarmTimeStep.TimeHolder(hour, minutes);
timeStep.restoreStepData(time);
}

if(savedInstanceState != null && savedInstanceState.containsKey(STATE_WEEK_DAYS)) {
boolean[] alarmDays = savedInstanceState.getBooleanArray(STATE_WEEK_DAYS);
daysStep.restoreStepData(alarmDays);
}

// IMPORTANT: The call to super method must be here at the end
super.onViewStateRestored(savedInstanceState);
}

private void goBack(Alarm alarm) {
NavController navController = NavHostFragment.findNavController(this);
String alarmSerialized = alarm != null ? alarm.serialize() : "";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import android.widget.TimePicker;

import androidx.annotation.NonNull;

import java.io.Serializable;

import ernestoyaquello.com.verticalstepperform.Step;
import verticalstepperform.ernestoyaquello.com.verticalstepperform.R;

Expand Down Expand Up @@ -115,7 +117,7 @@ private void updatedAlarmTimeText() {
alarmTimeTextView.setText(getStepDataAsHumanReadableString());
}

public static class TimeHolder {
public static class TimeHolder implements Serializable {

public int hour;
public int minutes;
Expand All @@ -124,5 +126,21 @@ public TimeHolder(int hour, int minutes) {
this.hour = hour;
this.minutes = minutes;
}

public int getHour() {
return hour;
}

public void setHour(int hour) {
this.hour = hour;
}

public int getMinutes() {
return minutes;
}

public void setMinutes(int minutes) {
this.minutes = minutes;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import android.content.Context;
import android.view.View;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

Expand All @@ -12,7 +13,7 @@
*
* @param <T> The type of the data that the step will hold. E.g., For a user email, T = String
*/
public abstract class Step<T> {
public abstract class Step<T extends Serializable> {

private String originalNextButtonText;
private String title;
Expand Down Expand Up @@ -62,7 +63,7 @@ protected Step(String title, String subtitle, String nextButtonText) {
public abstract String getStepDataAsHumanReadableString();

/**
* Restores the step data. Useful for when restoring the state of the form.
* Restores the step data. Will be called automatically by the form view upon restoration.
*
* @param data The step data to restore.
*/
Expand Down Expand Up @@ -405,7 +406,11 @@ void closeInternal(boolean useAnimations) {
}
}

void restoreErrorState(boolean hasError) {
void restoreStepDataInternal(Serializable data) {
restoreStepData((T)data);
}

void restoreErrorStateInternal(boolean hasError) {
this.hasError = hasError;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -525,15 +525,15 @@ boolean isConfirmationStep() {
/**
* This step will just display a button that the user will have to click to complete the form.
*/
private class ConfirmationStep extends Step<Object> {
private class ConfirmationStep extends Step<Integer> {

ConfirmationStep() {
super("");
}

@Override
public Object getStepData() {
return null;
public Integer getStepData() {
return 0;
}

@Override
Expand All @@ -542,12 +542,12 @@ public String getStepDataAsHumanReadableString() {
}

@Override
public void restoreStepData(Object data) {
public void restoreStepData(Integer data) {
// No need to do anything here
}

@Override
protected IsDataValid isStepDataValid(Object stepData) {
protected IsDataValid isStepDataValid(Integer stepData) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import android.widget.ProgressBar;
import android.widget.ScrollView;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand All @@ -38,7 +39,6 @@ public class VerticalStepperFormView extends LinearLayout {
private StepperFormListener listener;
private KeyboardTogglingObserver keyboardTogglingObserver;
private List<StepHelper> stepHelpers;
private List<StepHelper> originalStepHelpers;
private boolean initialized;

private LinearLayout formContentView;
Expand Down Expand Up @@ -461,8 +461,6 @@ public int refreshFormProgress() {

/**
* Adds a step to the form in the specified position.
* Please note that this change will be lost on restoration events like rotation. Hence, it
* will be up to you to re-add the step (and its state) AFTER the form is restored without it.
*
* @param index The index where the step will be added.
* @param stepToAdd The step to add.
Expand Down Expand Up @@ -498,13 +496,12 @@ public boolean addStep(int index, Step<?> stepToAdd) {
goToStep(index, true);
}

listener.onStepAdded(index, stepToAdd);
return true;
}

/**
* Removes the step that is placed at the specified position.
* Please note that this change will be lost on restoration events like rotation. Hence, it
* will be up to you to remove the step again AFTER the form is restored with it on it.
*
* @param index The index where the step to delete is.
* @return True if the step was deleted successfully; false otherwise.
Expand Down Expand Up @@ -536,6 +533,7 @@ public boolean removeStep(int index) {
goToStep(stepToOpen, true);
}

listener.onStepRemoved(index);
return true;
}

Expand Down Expand Up @@ -812,8 +810,8 @@ private void onConstructed(Context context, AttributeSet attrs, int defStyleAttr

void initializeForm(StepperFormListener listener, StepHelper[] stepsArray) {
this.listener = listener;
this.originalStepHelpers = Arrays.asList(stepsArray);
this.stepHelpers = new ArrayList<>(originalStepHelpers);
this.stepHelpers = Arrays.asList(stepsArray);
this.stepHelpers = new ArrayList<>(stepHelpers);

progressBar.setMax(stepHelpers.size());

Expand Down Expand Up @@ -1031,16 +1029,18 @@ private boolean isKeyboardOpen() {
public Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();

boolean[] completedSteps = new boolean[originalStepHelpers.size()];
boolean[] errorSteps = new boolean[originalStepHelpers.size()];
String[] titles = new String[originalStepHelpers.size()];
String[] subtitles = new String[originalStepHelpers.size()];
String[] buttonTexts = new String[originalStepHelpers.size()];
String[] errorMessages = new String[originalStepHelpers.size()];
Serializable[] stepsData = new Serializable[stepHelpers.size()];
boolean[] completedSteps = new boolean[stepHelpers.size()];
boolean[] errorSteps = new boolean[stepHelpers.size()];
String[] titles = new String[stepHelpers.size()];
String[] subtitles = new String[stepHelpers.size()];
String[] buttonTexts = new String[stepHelpers.size()];
String[] errorMessages = new String[stepHelpers.size()];
for (int i = 0; i < completedSteps.length; i++) {
StepHelper stepHelper = originalStepHelpers.get(i);
StepHelper stepHelper = stepHelpers.get(i);
Step<?> step = stepHelper.getStepInstance();

stepsData[i] = step.getStepData();
completedSteps[i] = step.isCompleted();
errorSteps[i] = step.hasError();
titles[i] = step.getTitle();
Expand All @@ -1052,10 +1052,11 @@ public Parcelable onSaveInstanceState() {
}

StepHelper openStepHelper = getOpenStepHelper();
int openStepPosition = originalStepHelpers.indexOf(openStepHelper);
int openStepPosition = stepHelpers.indexOf(openStepHelper);

bundle.putParcelable("superState", super.onSaveInstanceState());
bundle.putInt("openStep", openStepPosition);
bundle.putSerializable("stepsData", stepsData);
bundle.putBooleanArray("completedSteps", completedSteps);
bundle.putBooleanArray("errorSteps", errorSteps);
bundle.putStringArray("titles", titles);
Expand All @@ -1080,9 +1081,11 @@ public void onRestoreInstanceState(Parcelable state) {
boolean[] completedSteps = bundle.getBooleanArray("completedSteps");
boolean[] errorSteps = bundle.getBooleanArray("errorSteps");
int positionToOpen = bundle.getInt("openStep");
Serializable[] stepsData = (Serializable[])bundle.getSerializable("stepsData");
state = bundle.getParcelable("superState");

restoreFromState(
stepsData,
positionToOpen,
completedSteps,
errorSteps,
Expand All @@ -1097,6 +1100,7 @@ public void onRestoreInstanceState(Parcelable state) {
}

private void restoreFromState(
Serializable[] stepsData,
int positionToOpen,
boolean[] completedSteps,
boolean[] errorSteps,
Expand All @@ -1110,7 +1114,8 @@ private void restoreFromState(
StepHelper stepHelper = stepHelpers.get(i);
Step<?> step = stepHelper.getStepInstance();

step.restoreErrorState(errorSteps[i]);
step.restoreStepDataInternal(stepsData[i]);
step.restoreErrorStateInternal(errorSteps[i]);
step.updateTitle(titles[i], false);
step.updateSubtitle(subtitles[i], false);
step.updateNextButtonText(buttonTexts[i], false);
Expand Down
Loading

0 comments on commit 4ca0ee6

Please sign in to comment.