44// See the LICENSE file in the project root for full license information.
55
66using System ;
7+ using System . Reactive ;
78using System . Reactive . Disposables ;
89using System . Reactive . Linq ;
10+ using System . Threading ;
911using Splat ;
1012
1113namespace ReactiveUI ;
@@ -16,38 +18,50 @@ namespace ReactiveUI;
1618public static class SuspensionHostExtensions
1719{
1820 /// <summary>
19- /// Observe changes to the AppState of a class derived from ISuspensionHost .
21+ /// Func used to load app state exactly once .
2022 /// </summary>
21- /// <typeparam name="T">The observable type.</typeparam>
23+ private static Func < IObservable < Unit > > ? ensureLoadAppStateFunc ;
24+
25+ /// <summary>
26+ /// Supsension driver reference field to prevent introducing breaking change.
27+ /// </summary>
28+ private static ISuspensionDriver ? suspensionDriver ;
29+
30+ /// <summary>
31+ /// Get the current App State of a class derived from ISuspensionHost.
32+ /// </summary>
33+ /// <typeparam name="T">The app state type.</typeparam>
2234 /// <param name="item">The suspension host.</param>
23- /// <returns>An observable of the app state.</returns>
24- public static IObservable < T > ObserveAppState < T > ( this ISuspensionHost item )
25- where T : class
35+ /// <returns>The app state.</returns>
36+ public static T GetAppState < T > ( this ISuspensionHost item )
2637 {
2738 if ( item is null )
2839 {
2940 throw new ArgumentNullException ( nameof ( item ) ) ;
3041 }
3142
32- return item . WhenAny ( suspensionHost => suspensionHost . AppState , observedChange => observedChange . Value )
33- . WhereNotNull ( )
34- . Cast < T > ( ) ;
43+ Interlocked . Exchange ( ref ensureLoadAppStateFunc , null ) ? . Invoke ( ) ;
44+
45+ return ( T ) item . AppState ! ;
3546 }
3647
3748 /// <summary>
38- /// Get the current App State of a class derived from ISuspensionHost.
49+ /// Observe changes to the AppState of a class derived from ISuspensionHost.
3950 /// </summary>
40- /// <typeparam name="T">The app state type.</typeparam>
51+ /// <typeparam name="T">The observable type.</typeparam>
4152 /// <param name="item">The suspension host.</param>
42- /// <returns>The app state.</returns>
43- public static T GetAppState < T > ( this ISuspensionHost item )
53+ /// <returns>An observable of the app state.</returns>
54+ public static IObservable < T > ObserveAppState < T > ( this ISuspensionHost item )
55+ where T : class
4456 {
4557 if ( item is null )
4658 {
4759 throw new ArgumentNullException ( nameof ( item ) ) ;
4860 }
4961
50- return ( T ) item . AppState ! ;
62+ return item . WhenAny ( suspensionHost => suspensionHost . AppState , observedChange => observedChange . Value )
63+ . WhereNotNull ( )
64+ . Cast < T > ( ) ;
5165 }
5266
5367 /// <summary>
@@ -65,32 +79,64 @@ public static IDisposable SetupDefaultSuspendResume(this ISuspensionHost item, I
6579 }
6680
6781 var ret = new CompositeDisposable ( ) ;
68- driver ??= Locator . Current . GetService < ISuspensionDriver > ( ) ;
82+ suspensionDriver ??= driver ?? Locator . Current . GetService < ISuspensionDriver > ( ) ;
6983
70- if ( driver is null )
84+ if ( suspensionDriver is null )
7185 {
7286 item . Log ( ) . Error ( "Could not find a valid driver and therefore cannot setup Suspend/Resume." ) ;
7387 return Disposable . Empty ;
7488 }
7589
90+ ensureLoadAppStateFunc = ( ) => EnsureLoadAppState ( item , suspensionDriver ) ;
91+
7692 ret . Add ( item . ShouldInvalidateState
77- . SelectMany ( _ => driver . InvalidateState ( ) )
93+ . SelectMany ( _ => suspensionDriver . InvalidateState ( ) )
7894 . LoggedCatch ( item , Observables . Unit , "Tried to invalidate app state" )
7995 . Subscribe ( _ => item . Log ( ) . Info ( "Invalidated app state" ) ) ) ;
8096
8197 ret . Add ( item . ShouldPersistState
82- . SelectMany ( x => driver . SaveState ( item . AppState ! ) . Finally ( x . Dispose ) )
98+ . SelectMany ( x => suspensionDriver . SaveState ( item . AppState ! ) . Finally ( x . Dispose ) )
8399 . LoggedCatch ( item , Observables . Unit , "Tried to persist app state" )
84100 . Subscribe ( _ => item . Log ( ) . Info ( "Persisted application state" ) ) ) ;
85101
86102 ret . Add ( item . IsResuming . Merge ( item . IsLaunchingNew )
87- . SelectMany ( _ => driver . LoadState ( ) )
88- . LoggedCatch (
89- item ,
90- Observable . Defer ( ( ) => Observable . Return ( item . CreateNewAppState ? . Invoke ( ) ) ) ,
91- "Failed to restore app state from storage, creating from scratch" )
92- . Subscribe ( x => item . AppState = x ?? item . CreateNewAppState ? . Invoke ( ) ) ) ;
103+ . Do ( _ => Interlocked . Exchange ( ref ensureLoadAppStateFunc , null ) ? . Invoke ( ) )
104+ . Subscribe ( ) ) ;
93105
94106 return ret ;
95107 }
96- }
108+
109+ /// <summary>
110+ /// Ensures one time app state load from storage.
111+ /// </summary>
112+ /// <param name="item">The suspension host.</param>
113+ /// <param name="driver">The suspension driver.</param>
114+ /// <returns>A completed observable.</returns>
115+ private static IObservable < Unit > EnsureLoadAppState ( this ISuspensionHost item , ISuspensionDriver ? driver = null )
116+ {
117+ if ( item . AppState is not null )
118+ {
119+ return Observable . Return ( Unit . Default ) ;
120+ }
121+
122+ suspensionDriver ??= driver ?? Locator . Current . GetService < ISuspensionDriver > ( ) ;
123+
124+ if ( suspensionDriver is null )
125+ {
126+ item . Log ( ) . Error ( "Could not find a valid driver and therefore cannot load app state." ) ;
127+ return Observable . Return ( Unit . Default ) ;
128+ }
129+
130+ try
131+ {
132+ item . AppState = suspensionDriver . LoadState ( ) . Wait ( ) ;
133+ }
134+ catch ( Exception ex )
135+ {
136+ item . Log ( ) . Warn ( ex , "Failed to restore app state from storage, creating from scratch" ) ;
137+ item . AppState = item . CreateNewAppState ? . Invoke ( ) ;
138+ }
139+
140+ return Observable . Return ( Unit . Default ) ;
141+ }
142+ }
0 commit comments