Coverage Summary for Class: ISessionTrackerStorage (vit.khudenko.android.sessiontracker)

Class Method, % Block, % Line, %
ISessionTrackerStorage$SharedPrefsImpl 100% (9/9) 100% (4/4) 100% (33/33)
ISessionTrackerStorage$SharedPrefsImpl$Companion
Total 100% (9/9) 100% (4/4) 100% (33/33)


1 package vit.khudenko.android.sessiontracker 2  3 import android.annotation.SuppressLint 4 import android.content.SharedPreferences 5 import org.json.JSONArray 6 import org.json.JSONObject 7 import java.util.EnumSet 8  9 /** 10  * Your app must assume `ISessionTrackerStorage` methods may be called by [`SessionTracker`][SessionTracker] 11  * while processing the calls made by your app to the `SessionTracker`. 12  * 13  * `SessionTracker` calls `ISessionTrackerStorage` synchronously from the threads your application calls 14  * `SessionTracker` from. 15  * 16  * `SessionTracker` implementation guarantees that `ISessionTrackerStorage` methods are never called concurrently. 17  */ 18 interface ISessionTrackerStorage<State : Enum<State>> { 19  20  /** 21  * This method is called by `SessionTracker` from within the 22  * [`SessionTracker.trackSession()`][SessionTracker.trackSession] call. 23  * 24  * The implementation must not defer actual persisting for future. 25  * 26  * @param sessionRecord [`SessionRecord`][SessionRecord] 27  */ 28  fun createSessionRecord(sessionRecord: SessionRecord<State>) 29  30  /** 31  * This is called by `SessionTracker` from within the 32  * [`SessionTracker.initialize()`][SessionTracker.initialize] call. 33  * 34  * The implementation should read and create previously persisted (if any) list of [`SessionRecord`][SessionRecord] 35  * instances with corresponding states. If storage is empty, then an empty list should be returned. 36  */ 37  fun readAllSessionRecords(): List<SessionRecord<State>> 38  39  /** 40  * This method is called by `SessionTracker` from within the 41  * [`SessionTracker.consumeEvent()`][SessionTracker.consumeEvent] call. 42  * 43  * The implementation must not defer actual persisting for future. 44  * 45  * @param sessionRecord [`SessionRecord`][SessionRecord] 46  */ 47  fun updateSessionRecord(sessionRecord: SessionRecord<State>) 48  49  /** 50  * This method is called by `SessionTracker` from within the 51  * [`SessionTracker.untrackSession()`][SessionTracker.untrackSession] call. 52  * 53  * The implementation must not defer actual persisting for future. 54  * 55  * @param sessionId [`SessionId`][SessionId] 56  */ 57  fun deleteSessionRecord(sessionId: SessionId) 58  59  /** 60  * This method is called by `SessionTracker` from within the 61  * [`SessionTracker.untrackAllSessions()`][SessionTracker.untrackAllSessions] call. 62  * 63  * The implementation must not defer actual persisting for future. 64  */ 65  fun deleteAllSessionRecords() 66  67  class SharedPrefsImpl<State : Enum<State>>( 68  private val prefs: SharedPreferences, 69  stateEnumValues: EnumSet<State>, 70  ) : ISessionTrackerStorage<State> { 71  72  companion object { 73  private const val KEY_SESSION_RECORDS = "session_records" 74  private const val KEY_SESSION_ID = "id" 75  private const val KEY_SESSION_STATE = "state" 76  } 77  78  private val stateEnumValuesList: List<State> = stateEnumValues.toList() 79  80  override fun readAllSessionRecords(): List<SessionRecord<State>> { 81  val sessionRecords = mutableListOf<SessionRecord<State>>() 82  val jsonArray = JSONArray(prefs.getString(KEY_SESSION_RECORDS, "[]")) 83  for (i in 0 until jsonArray.length()) { 84  val sessionRecord = jsonToSessionRecord(jsonArray.getJSONObject(i)) 85  sessionRecords.add(sessionRecord) 86  } 87  return sessionRecords 88  } 89  90  override fun createSessionRecord(sessionRecord: SessionRecord<State>) { 91  saveSessionRecords( 92  readAllSessionRecords() + sessionRecord 93  ) 94  } 95  96  override fun updateSessionRecord(sessionRecord: SessionRecord<State>) { 97  saveSessionRecords( 98  readAllSessionRecords().map { 99  if (it.sessionId == sessionRecord.sessionId) { 100  sessionRecord 101  } else { 102  it 103  } 104  } 105  ) 106  } 107  108  override fun deleteSessionRecord(sessionId: SessionId) { 109  saveSessionRecords( 110  readAllSessionRecords().filter { it.sessionId != sessionId } 111  ) 112  } 113  114  override fun deleteAllSessionRecords() { 115  saveSessionRecords(emptyList()) 116  } 117  118  @SuppressLint("ApplySharedPref") 119  private fun saveSessionRecords(sessionRecords: List<SessionRecord<State>>) { 120  prefs.edit() 121  .putString( 122  KEY_SESSION_RECORDS, 123  JSONArray( 124  sessionRecords.map { sessionRecordToJson(it) } 125  ).toString() 126  ) 127  .commit() 128  } 129  130  private fun sessionRecordToJson(sessionRecord: SessionRecord<State>): JSONObject { 131  return JSONObject( 132  mapOf( 133  KEY_SESSION_ID to sessionRecord.sessionId.value, 134  KEY_SESSION_STATE to sessionRecord.state.ordinal 135  ) 136  ) 137  } 138  139  private fun jsonToSessionRecord(json: JSONObject): SessionRecord<State> { 140  return SessionRecord( 141  SessionId(json.getString(KEY_SESSION_ID)), 142  stateEnumValuesList[json.getInt(KEY_SESSION_STATE)] 143  ) 144  } 145  } 146 }