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 }