0

You can check this stackoverflow answer which uses a trick to override database path method in order to save db in sd card.

We are trying to do something similar with shared preferences without having to write and read files manually to sd card.

After a long search we found that there is this method getDataDirFile inside Android source code (android.app.ContextImpl) which is private and therefore cannot be overridden.

So is there another way to circumvent it?
Thanks

Community
  • 1
  • 1
George Pligoropoulos
  • 2,919
  • 3
  • 33
  • 65

2 Answers2

0

Sounds like a lot of work for something that is pretty easy. I just use this method:

public static Map<String, ?> getAll(Context context) {
    SharedPreferences sharedPrefs = PreferenceManager
            .getDefaultSharedPreferences(context);
    return sharedPrefs.getAll();
}

The map is serializable hence easy to save in a file or on a cloud.

I use this method to load the preferences into my app:

@SuppressWarnings("unchecked")
public static void loadPreferences(Context context, Map<String, ?> prefs) {
    SharedPreferences sharedPrefs = PreferenceManager
            .getDefaultSharedPreferences(context);
    for (String key : prefs.keySet()) {
            Object pref = prefs.get(key);
            if (pref instanceof Boolean) {
                sharedPrefs.edit().putBoolean(key, (Boolean) pref).commit();
            }
            if (pref instanceof Float) {
                sharedPrefs.edit().putFloat(key, (Float) pref).commit();
            }
            if (pref instanceof Integer) {
                sharedPrefs.edit().putInt(key, (Integer) pref).commit();
            }
            if (pref instanceof Long) {
                sharedPrefs.edit().putLong(key, (Long) pref).commit();
            }
            if (pref instanceof String) {
                sharedPrefs.edit().putString(key, (String) pref).commit();
            }
            if (pref instanceof Set<?>) {
                sharedPrefs.edit().putStringSet(key, (Set<String>) pref)
                        .commit();
            }
    }

}
cYrixmorten
  • 7,110
  • 3
  • 25
  • 33
  • This would only be used if someone would feel comfortable of massively overwriting preferences on save or reading entire preferences from file on each run. Also this would prevent to have use the PreferenceActivity because you would have to sync data on some time. Maybe this is possible with a callback but still not a pretty solution imho – George Pligoropoulos Feb 16 '14 at 23:38
  • 1
    Well, I simply only update it when exiting my PreferenceActivity. It is not a heavy task at all unless you have thousands of preferences. Have not noticed any performance impact yet. Also, I only read it when loading or updating a profile, hence the app uses the actual preference values on most occasions. – cYrixmorten Feb 17 '14 at 02:57
0

There are single datas that need to be saved by key name. This is why preferences are so useful.

However preferences can be distinguished in two parts. Those that are real settings that the user interacts with in the PreferenceActivity and those that are merely used to save temporary or permanent data that do not correspond explicitly to a check-box or something similar.

As far as the former goes, seems that cYrixmorten's answer is the best available but you have to be aware of some maintenance.

The latter could be saved in database in a specific table with two columns, one for key and one for value.

The following code implements such an idea and is created to give a similar experience of ease of use like native android preferences.

This Scala code is not considered the most elegant solution. Use it with care.

package myAndroidSqlite

import android.content.{ContentValues, Context}
import models.DatabaseHandler

/**
 * Created with IntelliJ IDEA.
 * Developer: pligor
 */
abstract class MyDbPreferencesCase[INNER_TYPE, OUTER_TYPE] {
  //abstract

  def preferenceKey: String

  def defaultValue: INNER_TYPE

  def getValue(implicit context: Context): OUTER_TYPE

  def setValue(newValue: OUTER_TYPE)(implicit context: Context): Boolean

  //concrete

  def isDefault(implicit context: Context, innerTypeManifest: Manifest[INNER_TYPE]) = {
    getInnerValue.asInstanceOf[INNER_TYPE] == defaultValue
  }

  def isEmpty(implicit context: Context, innerTypeManifest: Manifest[INNER_TYPE]) = isDefault

  private lazy val finalPreferenceKey: String = preferenceKey

  def clear(implicit context: Context, innerTypeManifest: Manifest[INNER_TYPE]): Unit = {
    setInnerValue(defaultValue)
  }

  /**
   * same as clear
   */
  def reset(implicit context: Context, innerTypeManifest: Manifest[INNER_TYPE]): Unit = {
    clear
  }

  private val booleanManifest = manifest[Boolean]
  private val floatManifest = manifest[Float]
  private val intManifest = manifest[Int]
  private val longManifest = manifest[Long]
  private val stringManifest = manifest[String]

  protected def getInnerValue(implicit context: Context,
                              innerTypeManifest: Manifest[INNER_TYPE]): Any = {
    createTableIfNotExists;

    val dbValue = getDBvalue

    writeNullValueIfNotExists;

    innerTypeManifest match {
      case `booleanManifest` => dbValue.map(_.toBoolean).getOrElse(defaultValue.asInstanceOf[Boolean])
      case `floatManifest` => dbValue.map(_.toFloat).getOrElse(defaultValue.asInstanceOf[Float])
      case `intManifest` => dbValue.map(_.toInt).getOrElse(defaultValue.asInstanceOf[Int])
      case `longManifest` => dbValue.map(_.toLong).getOrElse(defaultValue.asInstanceOf[Long])
      case `stringManifest` => dbValue.getOrElse(defaultValue.asInstanceOf[String])
    }
  }

  protected def setInnerValue(newValue: INNER_TYPE)
                             (implicit context: Context,
                              innerTypeManifest: Manifest[INNER_TYPE]): Boolean = {
    createTableIfNotExists;

    val stringedValue = innerTypeManifest match {
      case `booleanManifest` => newValue.asInstanceOf[Boolean].toString
      case `floatManifest` => newValue.asInstanceOf[Float].toString
      case `intManifest` => newValue.asInstanceOf[Int].toString
      case `longManifest` => newValue.asInstanceOf[Long].toString
      case `stringManifest` => newValue.asInstanceOf[String]
    }

    val dbIdOption = getDBid

    val contentValues = new ContentValues()

    contentValues.put(valueColumnName, stringedValue)

    if (dbIdOption.isDefined) {
      DatabaseHandler.getInstance.updateById(
        tableName = dbSharedPreferencesTableName,
        modelId = dbIdOption.get,
        contentValues = contentValues,
        columnName = autoIncColumnName
      )
    } else {
      contentValues.put(keyColumnName, finalPreferenceKey)

      MySQLiteOpenHelper.validateInsertion(
        DatabaseHandler.getInstance.insert(dbSharedPreferencesTableName, contentValues)
      )
    }
  }

  private val dbSharedPreferencesTableName = "android_shared_preferences"

  private val autoIncColumnName = "id"

  private val keyColumnName = "key"

  private val valueColumnName = "value"

  private def createTableIfNotExists(implicit context: Context): Unit = {
    DatabaseHandler.getInstance.workWithWritableDatabase {
      db =>
        db.execSQL(
          s"""CREATE TABLE IF NOT EXISTS $dbSharedPreferencesTableName (
            |$autoIncColumnName INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
            |$keyColumnName TEXT NOT NULL,
            |$valueColumnName TEXT NULL,
            |UNIQUE ($keyColumnName)
            |)""".stripMargin
        )
    }
  }

  private def getDBvalue(implicit context: Context) = {
    DatabaseHandler.getInstance.getSingleScalar[String](
      query = s"""SELECT "$valueColumnName" FROM $dbSharedPreferencesTableName WHERE "$keyColumnName" = ?""",
      Array[String](finalPreferenceKey)
    )
  }

  private def getDBid(implicit context: Context): Option[Long] = {
    DatabaseHandler.getInstance.getSingleScalar[Long](
      query = s"""SELECT $autoIncColumnName FROM $dbSharedPreferencesTableName WHERE "$keyColumnName" = ?""",
      Array[String](finalPreferenceKey)
    )
  }

  private def rowExists(implicit context: Context): Boolean = {
    DatabaseHandler.getInstance.exists(
      tableName = dbSharedPreferencesTableName,
      value = finalPreferenceKey,
      columnName = keyColumnName
    )
  }

  private def writeNullValueIfNotExists(implicit context: Context): Unit = {
    if (rowExists) {
      //no need to do anything
    } else {
      val contentValues = new ContentValues()

      contentValues.put(keyColumnName, finalPreferenceKey)

      contentValues.putNull(valueColumnName)

      DatabaseHandler.getInstance.insert(dbSharedPreferencesTableName, contentValues)
    }
  }
}

/*
TESTING ABOVE CLASS
////////////////////////////////////////////////////////////////////////////////////
{
          val booleanBeforeWrite = BooleanPreference.getValue

          log log s"booleanBeforeWrite: $booleanBeforeWrite"

          val booleanWriteSuccess = BooleanPreference.setValue(newValue = true)

          log log s"booleanWriteSuccess: $booleanWriteSuccess"

          val booleanAfterWrite = BooleanPreference.getValue

          log log s"booleanAfterWrite: $booleanAfterWrite"
        }


        {
          val intImmediateWrite = IntPreference.setValue(100)

          log log s"intImmediateWrite: $intImmediateWrite"

          val intWrittenValue = IntPreference.getValue

          log log s"intWrittenValue: $intWrittenValue"
        }

        {
          val longImmediateWrite = LongPreference.setValue(200L)

          log log s"longImmediateWrite: $longImmediateWrite"

          LongPreference.clear

          val longAfterClear = LongPreference.getValue

          log log s"longAfterClear: $longAfterClear"
        }

        {
          val floatBeforeWrite = FloatPreference.getValue

          log log s"floatBeforeWrite: $floatBeforeWrite"

          val floatWritten = FloatPreference.setValue(0.553F)

          log log s"floatWritten: $floatWritten"

          FloatPreference.reset

          val floatAfterReset = FloatPreference.getValue

          log log s"floatAfterReset: $floatAfterReset"
        }

        {
          val stringImmediateWrite = StringPreference.setValue("go go")

          log log s"stringImmediateWrite: $stringImmediateWrite"

          val stringAfterWrite = StringPreference.getValue

          log log s"stringAfterWrite: $stringAfterWrite"
        }
////////////////////////////////////////////////////////////////////////////////////
case object BooleanPreference extends MyDbPreferencesCase[Boolean, Boolean] {
  val preferenceKey: String = "BooleanPreference"

  val defaultValue: Boolean = false

  def getValue(implicit context: Context): Boolean = {
    getInnerValue.asInstanceOf[Boolean]
  }

  def setValue(newValue: Boolean)(implicit context: Context): Boolean = {
    setInnerValue(newValue)
  }
}

case object IntPreference extends MyDbPreferencesCase[Int, Int] {
  val preferenceKey: String = "IntPreference"

  val defaultValue: Int = -1

  def getValue(implicit context: Context): Int = {
    getInnerValue.asInstanceOf[Int]
  }

  def setValue(newValue: Int)(implicit context: Context): Boolean = {
    setInnerValue(newValue)
  }
}

case object FloatPreference extends MyDbPreferencesCase[Float, Float] {
  val preferenceKey: String = "FloatPreference"

  val defaultValue: Float = 0.1F

  def getValue(implicit context: Context): Float = {
    getInnerValue.asInstanceOf[Float]
  }

  def setValue(newValue: Float)(implicit context: Context): Boolean = {
    setInnerValue(newValue)
  }
}

case object LongPreference extends MyDbPreferencesCase[Long, Long] {
  val preferenceKey: String = "LongPreference"

  val defaultValue: Long = +1

  def getValue(implicit context: Context): Long = {
    getInnerValue.asInstanceOf[Long]
  }

  def setValue(newValue: Long)(implicit context: Context): Boolean = {
    setInnerValue(newValue)
  }
}

case object StringPreference extends MyDbPreferencesCase[String, String] {
  val preferenceKey: String = "StringPreference"

  val defaultValue: String = "tipota"

  def getValue(implicit context: Context): String = {
    getInnerValue.asInstanceOf[String]
  }

  def setValue(newValue: String)(implicit context: Context): Boolean = {
    setInnerValue(newValue)
  }
}
 */

Above source code takes for granted that you have already implemented a DatabaseHandler with the necessary methods

George Pligoropoulos
  • 2,919
  • 3
  • 33
  • 65