@@ -27,7 +27,9 @@ import javax.inject.Inject
2727import javax.inject.Singleton
2828import kotlin.coroutines.CoroutineContext
2929import kotlinx.coroutines.CoroutineScope
30+ import kotlinx.coroutines.flow.catch
3031import kotlinx.coroutines.launch
32+ import org.jetbrains.annotations.VisibleForTesting
3133
3234/* * Repository to persist session data to be shared between all app processes. */
3335internal interface SharedSessionRepository {
@@ -48,20 +50,38 @@ constructor(
4850 @Background private val backgroundDispatcher: CoroutineContext ,
4951) : SharedSessionRepository {
5052 /* * Local copy of the session data. Can get out of sync, must be double-checked in datastore. */
51- private lateinit var localSessionData: SessionData
53+ @VisibleForTesting lateinit var localSessionData: SessionData
54+
55+ /* *
56+ * Either notify the subscribers with general multi-process supported session or fallback local
57+ * session
58+ */
59+ private enum class NotificationType {
60+ GENERAL ,
61+ FALLBACK
62+ }
5263
5364 init {
65+ println (" session repo init" )
5466 CoroutineScope (backgroundDispatcher).launch {
55- sessionDataStore.data.collect { sessionData ->
56- localSessionData = sessionData
57- val sessionId = sessionData.sessionDetails.sessionId
58-
59- FirebaseSessionsDependencies .getRegisteredSubscribers().values.forEach { subscriber ->
60- // Notify subscribers, regardless of sampling and data collection state
61- subscriber.onSessionChanged(SessionSubscriber .SessionDetails (sessionId))
62- Log .d(TAG , " Notified ${subscriber.sessionSubscriberName} of new session $sessionId " )
67+ sessionDataStore.data
68+ .catch {
69+ val newSession =
70+ SessionData (
71+ sessionDetails = sessionGenerator.generateNewSession(null ),
72+ backgroundTime = timeProvider.currentTime()
73+ )
74+ Log .d(
75+ TAG ,
76+ " Init session datastore failed with exception message: ${it.message} . Emit fallback session ${newSession.sessionDetails.sessionId} "
77+ )
78+ emit(newSession)
79+ }
80+ .collect { sessionData ->
81+ localSessionData = sessionData
82+ val sessionId = sessionData.sessionDetails.sessionId
83+ notifySubscribers(sessionId, NotificationType .GENERAL )
6384 }
64- }
6585 }
6686 }
6787
@@ -74,9 +94,14 @@ constructor(
7494 Log .d(TAG , " App backgrounded on ${getProcessName()} - $sessionData " )
7595
7696 CoroutineScope (backgroundDispatcher).launch {
77- sessionDataStore.updateData {
78- // TODO(mrober): Double check time makes sense?
79- sessionData.copy(backgroundTime = timeProvider.currentTime())
97+ try {
98+ sessionDataStore.updateData {
99+ // TODO(mrober): Double check time makes sense?
100+ sessionData.copy(backgroundTime = timeProvider.currentTime())
101+ }
102+ } catch (ex: Exception ) {
103+ Log .d(TAG , " App backgrounded, failed to update data. Message: ${ex.message} " )
104+ localSessionData = localSessionData.copy(backgroundTime = timeProvider.currentTime())
80105 }
81106 }
82107 }
@@ -91,20 +116,47 @@ constructor(
91116
92117 if (shouldInitiateNewSession(sessionData)) {
93118 CoroutineScope (backgroundDispatcher).launch {
94- sessionDataStore.updateData { currentSessionData ->
95- // Double-check pattern
96- if (shouldInitiateNewSession(currentSessionData)) {
97- val newSessionDetails = sessionGenerator.generateNewSession(sessionData.sessionDetails)
98- sessionFirelogPublisher.mayLogSession(sessionDetails = newSessionDetails)
99- currentSessionData.copy(sessionDetails = newSessionDetails)
100- } else {
101- currentSessionData
119+ try {
120+ sessionDataStore.updateData { currentSessionData ->
121+ // Double-check pattern
122+ if (shouldInitiateNewSession(currentSessionData)) {
123+ val newSessionDetails =
124+ sessionGenerator.generateNewSession(sessionData.sessionDetails)
125+ sessionFirelogPublisher.mayLogSession(sessionDetails = newSessionDetails)
126+ currentSessionData.copy(sessionDetails = newSessionDetails)
127+ } else {
128+ currentSessionData
129+ }
102130 }
131+ } catch (ex: Exception ) {
132+ Log .d(TAG , " App appForegrounded, failed to update data. Message: ${ex.message} " )
133+ val newSessionDetails = sessionGenerator.generateNewSession(sessionData.sessionDetails)
134+ localSessionData = localSessionData.copy(sessionDetails = newSessionDetails)
135+ sessionFirelogPublisher.mayLogSession(sessionDetails = newSessionDetails)
136+
137+ val sessionId = newSessionDetails.sessionId
138+ notifySubscribers(sessionId, NotificationType .FALLBACK )
103139 }
104140 }
105141 }
106142 }
107143
144+ private suspend fun notifySubscribers (sessionId : String , type : NotificationType ) {
145+ FirebaseSessionsDependencies .getRegisteredSubscribers().values.forEach { subscriber ->
146+ // Notify subscribers, regardless of sampling and data collection state
147+ subscriber.onSessionChanged(SessionSubscriber .SessionDetails (sessionId))
148+ when (type) {
149+ NotificationType .GENERAL ->
150+ Log .d(TAG , " Notified ${subscriber.sessionSubscriberName} of new session $sessionId " )
151+ NotificationType .FALLBACK ->
152+ Log .d(
153+ TAG ,
154+ " Notified ${subscriber.sessionSubscriberName} of new fallback session $sessionId "
155+ )
156+ }
157+ }
158+ }
159+
108160 private fun shouldInitiateNewSession (sessionData : SessionData ): Boolean {
109161 val interval = timeProvider.currentTime() - sessionData.backgroundTime
110162 return interval > sessionsSettings.sessionRestartTimeout
0 commit comments