From 4ca0ee61fcb19eda61a23bdc4ba6875177afaeaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julio=20Ernesto=20Rodr=C3=ADguez=20Caba=C3=B1as?= Date: Thu, 13 Jan 2022 20:26:46 +0000 Subject: [PATCH] Improved restoration mechanism and added new callbacks --- README.md | 36 ----------- .../NewAlarmFormFragment.java | 59 ++++--------------- .../form/steps/AlarmTimeStep.java | 22 ++++++- .../com/verticalstepperform/Step.java | 11 +++- .../com/verticalstepperform/StepHelper.java | 10 ++-- .../VerticalStepperFormView.java | 37 +++++++----- .../listener/StepperFormListener.java | 17 ++++++ 7 files changed, 82 insertions(+), 110 deletions(-) diff --git a/README.md b/README.md index be1b9e9..e34df43 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/app/src/main/java/verticalstepperform/ernestoyaquello/com/verticalstepperform/NewAlarmFormFragment.java b/app/src/main/java/verticalstepperform/ernestoyaquello/com/verticalstepperform/NewAlarmFormFragment.java index b3b19d9..20bc5fc 100644 --- a/app/src/main/java/verticalstepperform/ernestoyaquello/com/verticalstepperform/NewAlarmFormFragment.java +++ b/app/src/main/java/verticalstepperform/ernestoyaquello/com/verticalstepperform/NewAlarmFormFragment.java @@ -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; @@ -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; @@ -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(() -> { @@ -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() : ""; diff --git a/app/src/main/java/verticalstepperform/ernestoyaquello/com/verticalstepperform/form/steps/AlarmTimeStep.java b/app/src/main/java/verticalstepperform/ernestoyaquello/com/verticalstepperform/form/steps/AlarmTimeStep.java index 8558800..0f4567a 100644 --- a/app/src/main/java/verticalstepperform/ernestoyaquello/com/verticalstepperform/form/steps/AlarmTimeStep.java +++ b/app/src/main/java/verticalstepperform/ernestoyaquello/com/verticalstepperform/form/steps/AlarmTimeStep.java @@ -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; @@ -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; @@ -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; + } } } diff --git a/vertical-stepper-form/src/main/java/ernestoyaquello/com/verticalstepperform/Step.java b/vertical-stepper-form/src/main/java/ernestoyaquello/com/verticalstepperform/Step.java index 1c2c3f4..6f9e252 100644 --- a/vertical-stepper-form/src/main/java/ernestoyaquello/com/verticalstepperform/Step.java +++ b/vertical-stepper-form/src/main/java/ernestoyaquello/com/verticalstepperform/Step.java @@ -3,6 +3,7 @@ import android.content.Context; import android.view.View; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; @@ -12,7 +13,7 @@ * * @param The type of the data that the step will hold. E.g., For a user email, T = String */ -public abstract class Step { +public abstract class Step { private String originalNextButtonText; private String title; @@ -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. */ @@ -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; } diff --git a/vertical-stepper-form/src/main/java/ernestoyaquello/com/verticalstepperform/StepHelper.java b/vertical-stepper-form/src/main/java/ernestoyaquello/com/verticalstepperform/StepHelper.java index 00c7500..c93a085 100644 --- a/vertical-stepper-form/src/main/java/ernestoyaquello/com/verticalstepperform/StepHelper.java +++ b/vertical-stepper-form/src/main/java/ernestoyaquello/com/verticalstepperform/StepHelper.java @@ -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 { + private class ConfirmationStep extends Step { ConfirmationStep() { super(""); } @Override - public Object getStepData() { - return null; + public Integer getStepData() { + return 0; } @Override @@ -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; } diff --git a/vertical-stepper-form/src/main/java/ernestoyaquello/com/verticalstepperform/VerticalStepperFormView.java b/vertical-stepper-form/src/main/java/ernestoyaquello/com/verticalstepperform/VerticalStepperFormView.java index 1c63864..82b816e 100644 --- a/vertical-stepper-form/src/main/java/ernestoyaquello/com/verticalstepperform/VerticalStepperFormView.java +++ b/vertical-stepper-form/src/main/java/ernestoyaquello/com/verticalstepperform/VerticalStepperFormView.java @@ -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; @@ -38,7 +39,6 @@ public class VerticalStepperFormView extends LinearLayout { private StepperFormListener listener; private KeyboardTogglingObserver keyboardTogglingObserver; private List stepHelpers; - private List originalStepHelpers; private boolean initialized; private LinearLayout formContentView; @@ -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. @@ -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. @@ -536,6 +533,7 @@ public boolean removeStep(int index) { goToStep(stepToOpen, true); } + listener.onStepRemoved(index); return true; } @@ -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()); @@ -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(); @@ -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); @@ -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, @@ -1097,6 +1100,7 @@ public void onRestoreInstanceState(Parcelable state) { } private void restoreFromState( + Serializable[] stepsData, int positionToOpen, boolean[] completedSteps, boolean[] errorSteps, @@ -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); diff --git a/vertical-stepper-form/src/main/java/ernestoyaquello/com/verticalstepperform/listener/StepperFormListener.java b/vertical-stepper-form/src/main/java/ernestoyaquello/com/verticalstepperform/listener/StepperFormListener.java index 0633e2c..a31b0ce 100644 --- a/vertical-stepper-form/src/main/java/ernestoyaquello/com/verticalstepperform/listener/StepperFormListener.java +++ b/vertical-stepper-form/src/main/java/ernestoyaquello/com/verticalstepperform/listener/StepperFormListener.java @@ -1,5 +1,7 @@ package ernestoyaquello.com.verticalstepperform.listener; +import ernestoyaquello.com.verticalstepperform.Step; + public interface StepperFormListener { /** @@ -21,4 +23,19 @@ public interface StepperFormListener { */ void onCancelledForm(); + /** + * It will get called when a new step is added dynamically via the method addStep() of the form. + * + * @param index The index where the step was added. + * @param addedStep The step that was added dynamically. + */ + void onStepAdded(int index, Step addedStep); + + /** + * It will get called when a step is removed dynamically via the method removeStep() of the form. + * + * @param index The index of the step that was removed. + */ + void onStepRemoved(int index); + }