Tag Archives: Java

Re-usable Auto-saver class for Android

It can also save CPU usage (and thus battery) by doing deferred updates to protect from “machine-gun” updates/saves on e.g. every keypress from the user.

Benefits:

  • can save CPU cycles, battery, storage medium wear, bandwidth
  • re-usable, encapsulated, separated from business logic
  • LGPL license

How I use it in my app (ttps://play.google.com/store/apps/details?id=com.dayagendaplanner&hl=en) :

  • Deferring/batching of saves to persistent store (SD card)
  • Deferring/batching updates to registered future alarm notifications
  • Deferring/batching of re-computations of times in the table. Although this is less sweet because AutoSaver works best if its use does not delay UI updates.

Here’s the code.

https://github.com/karol-depka/LibreLib/blob/master/LibreLibAndroid/src/com/karoldepka/librelib/android/AutoSaver.java :

package com.karoldepka.librelib.android;

import android.os.Handler;

/**
 * Re-usable class for batched/delayed saves/updates.
 * Save occurs: at most {@link #delayMs} milliseconds after modification is reported
 * (which is also the moment of timer restart).
 * After save, the timer stops.
 * After a modification is reported, timer restarts.
 * If the timer is already running, it will not start again (will run its course to the end),
 * otherwise there would be a risk of never saving if the modifications were reported
 * at certain big frequency.
 *
 * @author Karol Depka Pradzinski
 * @license LGPL
 */
public abstract class AutoSaver {
	public boolean saveTimerRunning = false;

	public class RunnableSave implements Runnable {
		@Override public void run() {
			saveNowIfNeeded();
			saveTimerRunning = false;
		}
	}

	private final int delayMs;
	private final Handler mHandler = new Handler();
	private Runnable runnableSave = new RunnableSave();

	public AutoSaver(int delayMs) {
		this.delayMs = delayMs;
	}

	protected abstract void saveCustom();

	public void documentModified() {
		ensureSaveTimerRunning();
	}

	private void ensureSaveTimerRunning() {
		if ( !saveTimerRunning ) {
			mHandler.postDelayed(runnableSave, delayMs);
			saveTimerRunning = true;
		}
	}

	public void saveNowIfNeeded() {
		// TODO: only save if modified
		forceSaveNow();
	}

	/** Useful e.g. when shutting down the app*/
	public void forceSaveNow() {
		saveCustom();
		stopSaveTimer();
	}

	private void stopSaveTimer() {
		mHandler.removeCallbacks(runnableSave);
		saveTimerRunning = false;
	}

	public void destroy() {
		saveNowIfNeeded();
	}
}

Example usage:

https://github.com/karol-depka/LibreLib/blob/master/LibreLibAndroidExamples/src/com/example/librelib/android/examples/AutoSaverExample.java :

package com.example.librelib.android.examples;

import com.karoldepka.librelib.android.AutoSaver;

public class AutoSaverExample {
package com.example.librelib.android.examples;

import com.karoldepka.librelib.android.AutoSaver;

public class AutoSaverExample {

	private final AutoSaver autoSaver = new AutoSaver(2000) {
		@Override public void saveCustom() {
			// save/push/update somewhere
		}
	};

	/** Called when the user modifies the data */
	public void onModified() {
		autoSaver.documentModified();
	}

	public void onForceSave() {
		autoSaver.forceSaveNow();
	}

	public void onDestroy() {
		autoSaver.destroy();
	}

}

Creative Commons License

This work is licensed under a Creative Commons Attribution 3.0 Unported License.

Advertisements

More responsible (atomic) way to write files in Java – AtomicFileWriter

Did You ever have a situation in which a certain application has corrupted its file (e.g. config) by leaving incomplete or empty file due to an error during writing of the file?

Here is my proposed solution to prevent this and thus make applications more robust.

package com.karoldepka.librelib.android.io;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

/** Writes to a file in such a way to ensure that the previous contents
 * of the file will stay intact if the writing does not complete successfully.
 * 
 * Prevents a scenario in which the target file is left corrupted
 * with incomplete (or even empty) content due to failed attempt to write.
 * 
 * @author Karol Depka Pradzinski
 * @license LGPL
 */
public class AtomicFileWriter extends BufferedWriter {

	private final AtomicFileHandler partFileHandler;
	
	public AtomicFileWriter(File file, boolean append)
			throws IOException {
		super(new FileWriter(AtomicFileHandler.getPartFile(file), append));
		partFileHandler = new AtomicFileHandler(file);
		
	}
	
	@Override
	public void close() throws IOException {
		super.close();
		partFileHandler.onClose();
	}

}
package com.karoldepka.librelib.android.io;

import java.io.File;

/**
 * Handles partial/old file for atomic writing.
 * 
 * Logic in this class is separated from AtomicFileWriter to avoid tying it
 * to Writer (as opposed to other uses in e.g. OutputStream).
 * 
 * @author Karol Depka Pradzinski
 * @license LGPL
 */
public class AtomicFileHandler {
	private final File targetFile;
	
	public AtomicFileHandler(File targetFile) {
		this.targetFile = targetFile;
	}
	
	public static File getPartFile(File file) {
		return getFileWithSuffix(file, ".part");
	}

	public static File getFileWithSuffix(File file, String suffix) {
		return new File(file.getParentFile(), file.getName()+suffix);
	}
	
	public void onClose() {
		renamePartFileToTarget();
	}

	public void renamePartFileToTarget() {
		File partFile = getPartFile();
		File fileWithOldSuffix = getFileWithSuffix(targetFile, ".old");
		
		if ( targetFile.exists() ) {
			targetFile.renameTo(fileWithOldSuffix);
		}
		partFile.renameTo(targetFile);
		fileWithOldSuffix.delete();
	}

	public File getPartFile() {
		return getPartFile(targetFile);
	}

}

More strongly enforced canonical mappings using PutOnceMap

Ever used a Map for a canonical mapping? But what if someone due to a bug, creates another instance when a canonical instance is already “canonized”?

This class ensures this potential bug to be fail-fast.

package com.karoldepka.librelib;

import java.util.HashMap;

/**
 * A HashMap for canonical mapping which ensures fail-fast behavior if someone tries
 * to change the mapping of an already "canonized" key+value.
 * 
 * @author Karol Depka Pradzinski
 * @license LGPL
 */
public class PutOnceMap<TKey, TValue> extends HashMap<TKey, TValue> {
	
	@Override
	public TValue put(TKey key, TValue value) {
		TValue currentVal = get(key);
		if ( currentVal != null ) {
			if ( currentVal != value ) {
				throw new RuntimeException("Cannot change mapping for key " +key + " to value " +
						value + " from value " + get(key));
			} else {
				// unnecessary put
				return currentVal;
			}
		} else {
			return super.put(key,value);
		}
		
	}
	
}

Checking a CharSequence for equality – use contentEquals instead of equals, or you will have a bug

In Android, You sometimes get CharSequence as a parameter of a callback.

It might be tempting to check for equality using  “equals” method of a String.

But this would be a bug:

"SomethingSomething".equals(myCharSequence)

because equals requires the other object to be an instance of String as well, as explained in JavaDoc of String.equals :

public boolean equals(Object anObject)

Compares this string to the specified object. The result is true if and only if the argument is not null and is a String object that represents the same sequence of characters as this object.

The correct way would be to use contentEquals:

"SomethingSomething".contentEquals(myCharSequence)