From 77eb3b198a2a963eed685fd9d7833b03f2999523 Mon Sep 17 00:00:00 2001 From: Andrii Chebukin Date: Tue, 31 Aug 2021 22:21:05 +0300 Subject: [PATCH] Simplified incremental loading usage --- .../IncrementalLoadingCollection.fs | 24 ++++++++- .../IncrementalLoadingCollection.cs | 12 +++-- src/Elmish.Uno.Uwp/ViewModel.cs | 6 +-- src/Elmish.Uno/Binding.fs | 2 +- src/Samples/Samples/Exception.fs | 13 +++++ src/Samples/Samples/OneWaySeq.fs | 52 +++++++++++++------ src/Samples/Samples/Samples.fsproj | 1 + 7 files changed, 84 insertions(+), 26 deletions(-) create mode 100644 src/Samples/Samples/Exception.fs diff --git a/src/Elmish.Uno.Mobile/IncrementalLoadingCollection.fs b/src/Elmish.Uno.Mobile/IncrementalLoadingCollection.fs index a1623d96..bd4473a5 100644 --- a/src/Elmish.Uno.Mobile/IncrementalLoadingCollection.fs +++ b/src/Elmish.Uno.Mobile/IncrementalLoadingCollection.fs @@ -9,7 +9,7 @@ type IncrementalLoadingCollection<'t> = inherit ObservableCollection<'t> val has: unit -> bool - val load: uint * TaskCompletionSource -> unit + val load: uint * (uint -> unit) -> unit new (hasMoreItems, loadMoreItems) = { inherit ObservableCollection<'t>(); @@ -32,8 +32,28 @@ type IncrementalLoadingCollection<'t> = member this.LoadMoreItemsAsync (count) = let tcs = TaskCompletionSource() - this.load (count, tcs) + this.load (count, fun count -> tcs.SetResult(count)) let mapToResult (actualCountTask: Task) = LoadMoreItemsResult(Count = actualCountTask.Result) tcs.Task.ContinueWith(mapToResult, TaskContinuationOptions.OnlyOnRanToCompletion).AsAsyncOperation() + //(async { + // let tcs = TaskCompletionSource() + // let count = + // try + // let! count = this.load (count, fun count -> tcs.SetResult(count)) + // count + // with + // | :? Exception as ex -> + // tcs.SetException(ex) + // ex.Reraise () + // let mapToResult (actualCountTask: Task) = + // LoadMoreItemsResult(Count = actualCountTask.Result) + // tcs.Task.ContinueWith(mapToResult, TaskContinuationOptions.OnlyOnRanToCompletion).AsAsyncOperation() + // let! count = load count + // this.load (count, tcs) + // let complete (actualCount: uint) = async { + // return LoadMoreItemsResult(Count = actualCountTask.Result) + // } + //} |> Async.StartAsTask()).ContinueWith(mapToResult, TaskContinuationOptions.OnlyOnRanToCompletion).AsAsyncOperation() + diff --git a/src/Elmish.Uno.Uwp/IncrementalLoadingCollection.cs b/src/Elmish.Uno.Uwp/IncrementalLoadingCollection.cs index d07eff6a..aefe0651 100644 --- a/src/Elmish.Uno.Uwp/IncrementalLoadingCollection.cs +++ b/src/Elmish.Uno.Uwp/IncrementalLoadingCollection.cs @@ -3,6 +3,8 @@ using System.Collections.ObjectModel; using System.Threading.Tasks; +using Microsoft.FSharp.Core; + using Windows.Foundation; using Windows.UI.Xaml.Data; @@ -12,21 +14,21 @@ internal class IncrementalLoadingCollection : ObservableCollection, ISuppo { private readonly Func hasMoreItems; - private readonly Action> loadMoreItems; + private readonly Action> loadMoreItems; - public IncrementalLoadingCollection(Func hasMoreItems, Action> loadMoreItems) + public IncrementalLoadingCollection(Func hasMoreItems, Action> loadMoreItems) { this.hasMoreItems = hasMoreItems; this.loadMoreItems = loadMoreItems; } - public IncrementalLoadingCollection(IEnumerable collection, Func hasMoreItems, Action> loadMoreItems) : base(collection) + public IncrementalLoadingCollection(IEnumerable collection, Func hasMoreItems, Action> loadMoreItems) : base(collection) { this.hasMoreItems = hasMoreItems; this.loadMoreItems = loadMoreItems; } - public IncrementalLoadingCollection(List list, Func hasMoreItems, Action> loadMoreItems) : base(list) + public IncrementalLoadingCollection(List list, Func hasMoreItems, Action> loadMoreItems) : base(list) { this.hasMoreItems = hasMoreItems; this.loadMoreItems = loadMoreItems; @@ -39,7 +41,7 @@ public IAsyncOperation LoadMoreItemsAsync(uint count) async Task LoadMoreItemsAsync () { var tcs = new TaskCompletionSource(); - loadMoreItems(count, tcs); + loadMoreItems(count, FuncConvert.FromAction(c => tcs.SetResult(c))); var actualCount = await tcs.Task; return new LoadMoreItemsResult { Count = actualCount }; } diff --git a/src/Elmish.Uno.Uwp/ViewModel.cs b/src/Elmish.Uno.Uwp/ViewModel.cs index a1507a59..d2b30d6a 100644 --- a/src/Elmish.Uno.Uwp/ViewModel.cs +++ b/src/Elmish.Uno.Uwp/ViewModel.cs @@ -60,11 +60,11 @@ string GetBindingType(Binding binding) public override ViewModelBase Create(TSubModel initialModel, FSharpFunc dispatch, FSharpList> bindings, ElmConfig config, string propNameChain) => new ViewModel(initialModel, dispatch, bindings, config, propNameChain); - public override ObservableCollection CreateCollection(FSharpFunc hasMoreItems, FSharpFunc>, TMsg> loadMoreitems, System.Collections.Generic.IEnumerable collection) + public override ObservableCollection CreateCollection(FSharpFunc hasMoreItems, FSharpFunc>, TMsg> loadMoreitems, System.Collections.Generic.IEnumerable collection) { - void LoadMoreitems(uint count, TaskCompletionSource tcs) + void LoadMoreitems(uint count, FSharpFunc complete) { - var msg = loadMoreitems.Invoke(new Tuple>(count, tcs)); + var msg = loadMoreitems.Invoke(new Tuple>(count, complete)); Dispatch.Invoke(msg); } return new IncrementalLoadingCollection(collection, () => hasMoreItems.Invoke(this.currentModel), LoadMoreitems); diff --git a/src/Elmish.Uno/Binding.fs b/src/Elmish.Uno/Binding.fs index e7bf4e6c..e3ebe03c 100644 --- a/src/Elmish.Uno/Binding.fs +++ b/src/Elmish.Uno/Binding.fs @@ -7,7 +7,7 @@ open Elmish open System.Threading.Tasks type internal HasMoreItems<'model> = 'model -> bool -type internal LoadMoreItems<'msg> = uint * TaskCompletionSource -> 'msg +type internal LoadMoreItems<'msg> = uint * (uint -> unit) -> 'msg [] type internal IncrementalLoadingData<'model,'msg> = | Static diff --git a/src/Samples/Samples/Exception.fs b/src/Samples/Samples/Exception.fs new file mode 100644 index 00000000..72cee660 --- /dev/null +++ b/src/Samples/Samples/Exception.fs @@ -0,0 +1,13 @@ +[] +[] +module System.Exception + +open System +open System.Runtime.ExceptionServices + +// Useful for reraising exceptions under an async{...} context +// See this for more details: https://github.com/fsharp/fslang-suggestions/issues/660 +type Exception with + member __.Reraise () = + (ExceptionDispatchInfo.Capture __).Throw () + Unchecked.defaultof<_> diff --git a/src/Samples/Samples/OneWaySeq.fs b/src/Samples/Samples/OneWaySeq.fs index 1b897ab0..5d28a0bb 100644 --- a/src/Samples/Samples/OneWaySeq.fs +++ b/src/Samples/Samples/OneWaySeq.fs @@ -4,37 +4,59 @@ open System.Threading.Tasks open FSharp.Collections.Immutable open Elmish open Elmish.Uno +open System type Model = { OneWaySeqNumbers: int list OneWayNumbers: int list - IncrementalLoadingNumbers: int FlatList } + IncrementalLoadingNumbers: int FlatList + IsLoading: bool } let initial = { OneWaySeqNumbers = [ 1000..-1..1 ] OneWayNumbers = [ 1000..-1..1 ] - IncrementalLoadingNumbers = [ 1..1..10 ] |> FlatList.ofSeq } + IncrementalLoadingNumbers = [ 1..1..10 ] |> FlatList.ofSeq + IsLoading = false } -let init () = initial +let init () = initial, Cmd.none type Msg = | AddOneWaySeqNumber | AddOneWayNumber - | LoadMore of count: uint * tcs: TaskCompletionSource + | LoadMore of count: uint * complete: (uint -> unit) + | LoadedMore of allItems: FlatList + | ErrorLoadingMore + +let asyncLoadItems (complete: uint -> unit) (items: FlatList) count = async { + try + try + let intCount = int count + let builder = items.ToBuilder() + let max = FlatList.last items + for i = max + 1 to max + intCount do + builder.Add(i) + return LoadedMore <| builder.ToImmutable () + with _ -> + return ErrorLoadingMore + finally + // Must be called to complete Task and unblock UI to update + // Otherwise it will block loading async operaiton and it will never be called again + complete count + } let update msg m = match msg with - | AddOneWaySeqNumber -> { m with OneWaySeqNumbers = m.OneWaySeqNumbers.Head + 1 :: m.OneWaySeqNumbers } - | AddOneWayNumber -> { m with OneWayNumbers = m.OneWayNumbers.Head + 1 :: m.OneWayNumbers } - | LoadMore (count, tcs) -> - let intCount = int count - let builder = m.IncrementalLoadingNumbers.ToBuilder() - let max = FlatList.last m.IncrementalLoadingNumbers - for i = max + 1 to max + intCount do - builder.Add(i) - tcs.SetResult(count) - { m with IncrementalLoadingNumbers = builder.ToImmutable() } + | AddOneWaySeqNumber -> { m with OneWaySeqNumbers = m.OneWaySeqNumbers.Head + 1 :: m.OneWaySeqNumbers }, Cmd.none + | AddOneWayNumber -> { m with OneWayNumbers = m.OneWayNumbers.Head + 1 :: m.OneWayNumbers }, Cmd.none + | LoadMore (count, complete) -> + // If error hapened earlier it may be worth to do someting with that instead + { m with IsLoading = true }, // Display some hint (indetermined progress bar) + asyncLoadItems complete m.IncrementalLoadingNumbers count |> Cmd.OfAsync.result + | LoadedMore items -> { m with + IsLoading = false // Hide loading indicator + IncrementalLoadingNumbers = items }, Cmd.none + | ErrorLoadingMore -> m, Cmd.none // add error to model that will appear on IU let bindings : Binding list = [ "OneWaySeqNumbers" |> Binding.oneWaySeq ((fun m -> m.OneWaySeqNumbers), (=), id) @@ -49,7 +71,7 @@ let designModel = initial [] let program = - Program.mkSimpleUno init update bindings + Program.mkProgramUno init update bindings |> Program.withConsoleTrace [] diff --git a/src/Samples/Samples/Samples.fsproj b/src/Samples/Samples/Samples.fsproj index 46312641..1f88f339 100644 --- a/src/Samples/Samples/Samples.fsproj +++ b/src/Samples/Samples/Samples.fsproj @@ -7,6 +7,7 @@ +