refactor autosave: replace Timer by Handler

This commit is contained in:
korelstar 2017-09-26 16:49:50 +02:00
parent 4529801fce
commit 7ead3f9e45

View file

@ -2,8 +2,9 @@ package it.niedermann.owncloud.notes.android.fragment;
import android.app.Fragment; import android.app.Fragment;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.text.Editable; import android.text.Editable;
@ -11,21 +12,14 @@ import android.text.TextWatcher;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView; import android.widget.TextView;
import com.yydcdut.rxmarkdown.RxMDEditText; import com.yydcdut.rxmarkdown.RxMDEditText;
import com.yydcdut.rxmarkdown.RxMarkdown; import com.yydcdut.rxmarkdown.RxMarkdown;
import com.yydcdut.rxmarkdown.factory.EditFactory; import com.yydcdut.rxmarkdown.factory.EditFactory;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.model.DBNote; import it.niedermann.owncloud.notes.model.DBNote;
import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper;
@ -38,13 +32,16 @@ public class NoteEditFragment extends Fragment implements NoteFragmentI {
public static final String PARAM_NOTE = "note"; public static final String PARAM_NOTE = "note";
private static final String LOG_TAG = "NoteEditFragment"; private static final String LOG_TAG = "NoteEditFragment";
private static final long DELAY = 2000; // in ms private static final String LOG_TAG_AUTOSAVE = "AutoSave";
private static final long DELAY_AFTER_SYNC = 5000; // in ms
private static final long DELAY = 2000; // Wait for this time after typing before saving
private static final long DELAY_AFTER_SYNC = 5000; // Wait for this time after saving before checking for next save
private static final long DELAY_SHOW_SAVED = 1000; // How long "saved" is shown
private DBNote note; private DBNote note;
private Timer timer, timerNextSync;
private boolean saveActive = false;
private NoteSQLiteOpenHelper db; private NoteSQLiteOpenHelper db;
private Handler handler;
private boolean saveActive, unsavedEdit;
public static NoteEditFragment newInstance(DBNote note) { public static NoteEditFragment newInstance(DBNote note) {
NoteEditFragment f = new NoteEditFragment(); NoteEditFragment f = new NoteEditFragment();
@ -65,6 +62,7 @@ public class NoteEditFragment extends Fragment implements NoteFragmentI {
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setHasOptionsMenu(true); setHasOptionsMenu(true);
handler = new Handler(Looper.getMainLooper());
} }
@Override @Override
@ -109,61 +107,68 @@ public class NoteEditFragment extends Fragment implements NoteFragmentI {
content.setText(charSequence, TextView.BufferType.SPANNABLE); content.setText(charSequence, TextView.BufferType.SPANNABLE);
} }
}); });
}
content.addTextChangedListener(new TextWatcher() { private final TextWatcher textWatcher = new TextWatcher() {
@Override @Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(final CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(final Editable s) {
unsavedEdit = true;
if(!saveActive) {
handler.removeCallbacks(runAutoSave);
handler.postDelayed(runAutoSave, DELAY);
} }
}
};
@Override @Override
public void onTextChanged(final CharSequence s, int start, int before, int count) { public void onResume() {
if (timer != null) { super.onResume();
timer.cancel(); getContentView().addTextChangedListener(textWatcher);
timer = null;
}
}
@Override
public void afterTextChanged(final Editable s) {
if(timer != null) {
timer.cancel();
}
if(!saveActive) {
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
if(getActivity() != null)
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
autoSave();
}
});
}
}, DELAY);
}
}
});
} }
@Override @Override
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
getContentView().removeTextChangedListener(textWatcher);
cancelTimers(); cancelTimers();
} }
private final Runnable runAutoSave = new Runnable() {
@Override
public void run() {
if(unsavedEdit) {
Log.d(LOG_TAG_AUTOSAVE, "runAutoSave: start AutoSave");
autoSave();
} else {
Log.d(LOG_TAG_AUTOSAVE, "runAutoSave: nothing changed");
}
}
};
private final Runnable runResetActionBar = new Runnable() {
@Override
public void run() {
ActionBar actionBar = getActionBar();
if(actionBar == null) {
Log.w(LOG_TAG_AUTOSAVE, "runResetActionBar: ActionBar NOT AVAILABLE!");
return;
}
Log.d(LOG_TAG_AUTOSAVE, "runResetActionBar: reset action bar");
actionBar.setSubtitle(getString(R.string.action_edit_editing));
}
};
private void cancelTimers() { private void cancelTimers() {
if (timer != null) { handler.removeCallbacks(runAutoSave);
timer.cancel(); handler.removeCallbacks(runResetActionBar);
timer = null;
}
if (timerNextSync != null) {
timerNextSync.cancel();
timerNextSync = null;
}
saveActive = false;
} }
private RxMDEditText getContentView() { private RxMDEditText getContentView() {
@ -176,20 +181,32 @@ public class NoteEditFragment extends Fragment implements NoteFragmentI {
* @return String of the current content. * @return String of the current content.
*/ */
private String getContent() { private String getContent() {
return ((EditText) getContentView()).getText().toString(); return getContentView().getText().toString();
}
private ActionBar getActionBar() {
AppCompatActivity activity = (AppCompatActivity) getActivity();
if(activity == null) {
return null;
}
return activity.getSupportActionBar();
} }
/** /**
* Saves the current changes and show the status in the ActionBar * Saves the current changes and show the status in the ActionBar
*/ */
private void autoSave() { private void autoSave() {
Log.d(LOG_TAG, "START save+sync"); Log.d(LOG_TAG_AUTOSAVE, "STARTAUTOSAVE");
final ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar(); ActionBar actionBar = getActionBar();
// if fragment is not attached to activity, then there is nothing to save
if (getActivity() == null) {
Log.w(LOG_TAG_AUTOSAVE, "autoSave: Activity NOT AVAILABLE!");
return;
}
saveActive = true; saveActive = true;
if (actionBar != null) { if (actionBar != null) {
actionBar.setSubtitle(getString(R.string.action_edit_saving)); actionBar.setSubtitle(getString(R.string.action_edit_saving));
} }
final String content = getContent();
saveData(new ICallback() { saveData(new ICallback() {
@Override @Override
public void onFinish() { public void onFinish() {
@ -201,50 +218,23 @@ public class NoteEditFragment extends Fragment implements NoteFragmentI {
onSaved(); onSaved();
} }
public void onSaved() { private void onSaved() {
// AFTER SYNCHRONIZATION // AFTER SYNCHRONIZATION
Log.d(LOG_TAG, "...sync finished"); Log.d(LOG_TAG_AUTOSAVE, "FINISHED AUTOSAVE");
if (getActivity() != null && actionBar != null) { saveActive = false;
actionBar.setTitle(note.getTitle()); ActionBar actionBar = getActionBar();
actionBar.setSubtitle(getResources().getString(R.string.action_edit_saved)); if (actionBar == null) {
Executors.newSingleThreadScheduledExecutor().schedule(new Runnable() { Log.w(LOG_TAG_AUTOSAVE, "autoSave/onSaved: ActionBar NOT AVAILABLE!");
@Override return;
public void run() {
if (getActivity() != null) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// AFTER 1 SECOND: set ActionBar to default title
if (getActivity() != null && actionBar != null)
actionBar.setSubtitle(getString(R.string.action_edit_editing));
}
});
}
}
}, 1, TimeUnit.SECONDS);
timerNextSync = new Timer();
timerNextSync.schedule(new TimerTask() {
@Override
public void run() {
if (getActivity() != null) {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// AFTER "DELAY_AFTER_SYNC" SECONDS: allow next auto-save or start it directly
if (getContent().equals(content)) {
saveActive = false;
Log.d(LOG_TAG, "FINISH, no new changes");
} else {
Log.d(LOG_TAG, "content has changed meanwhile -> restart save");
autoSave();
}
}
});
}
}
}, DELAY_AFTER_SYNC);
} }
actionBar.setTitle(note.getTitle());
actionBar.setSubtitle(getResources().getString(R.string.action_edit_saved));
// AFTER "DELAY_SHOW_SAVED": set ActionBar to default title
handler.postDelayed(runResetActionBar, DELAY_SHOW_SAVED);
// AFTER "DELAY_AFTER_SYNC" SECONDS: allow next auto-save or start it directly
handler.postDelayed(runAutoSave, DELAY_AFTER_SYNC);
} }
}); });
@ -253,10 +243,11 @@ public class NoteEditFragment extends Fragment implements NoteFragmentI {
/** /**
* Save the current state in the database and schedule synchronization if needed. * Save the current state in the database and schedule synchronization if needed.
* *
* @param callback * @param callback Observer which is called after save/synchronization
*/ */
private void saveData(ICallback callback) { private void saveData(ICallback callback) {
Log.d(LOG_TAG, "saveData()"); Log.d(LOG_TAG, "saveData()");
note = db.updateNoteAndSync(note, getContent(), callback); note = db.updateNoteAndSync(note, getContent(), callback);
unsavedEdit = false;
} }
} }