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 }