reactiveui / Akavache

An asynchronous, persistent key-value store created for writing desktop and mobile applications, based on SQLite3. Akavache is great for both storing important data as well as cached local data that expires.
https://evolve.xamarin.com/session/56e2044afd00c0253cae33a3
MIT License
2.44k stars 288 forks source link

Caching, the right way? #268

Open Mittchel opened 8 years ago

Mittchel commented 8 years ago

Hi,

I really love Akavache and I think it's awesome. I noticed across the web that a there are a lot of tutorials, but to be honest most of them aren't correct. They always await the call so they only get back the cached result and thus do not understand the Subscribe is called twice.

I'm keen on writing a blogpost about the best way of implementing Caching (with Akavache) from front-end to service call. So UI updates etcetera. I've come a pretty long way, but I have the feeling it can be simplified. So I hope someone is able to review this code and maybe tell me what could be improved so there is less overhead. I really like to see your opinion on this @paulcbetts.

private async void LoadMessages()
{
    // Service call that uses refit
    IObservable<GetMessagesResponse> messages = _ticketService.GetMessages (_selectedTicketId);

    // Hold the cached result and result from server
    List<LatestMessageDto> serverResult = new List<LatestMessageDto>();
    List<LatestMessageDto> cachedResult = new List<LatestMessageDto>();

    // This method is fired twice (Cache + Remote data)
    messages.Subscribe ( subscribedPosts => 
    {
        // Clear the list so the second time there is remote data in there
        serverResult.Clear();
        serverResult.AddRange(subscribedPosts.data);

        // Find differences in cache and remote.
        var newItems = serverResult.Except(cachedResult, new MessageComparer()).Reverse().ToList();

        // Show new data to the UI.
        AddMessages(newItems);
    });

    // Retrieve the cached results (on load)
    var cache = await messages.FirstOrDefaultAsync();
    cachedResult.AddRange (cache.data);
    cachedResult.Reverse ();

    // Add initial cached result to UI
    AddMessages (cachedResult);
}

Code for calling remote:

public IObservable<GetMessagesResponse> GetMessages(int ticketId)
{
    var cache = BlobCache.LocalMachine;

    var cachedPostsPromise = cache.GetAndFetchLatest(ticketId.ToString(), () => GetMessagesRemote(ticketId),
    offset =>
    {
        return true;
    });

    return cachedPostsPromise;
}
anaisbetts commented 8 years ago

That's not bad - the problem is that how to handle the code in your Subscribe call (i.e. how to merge the cached and new versions) is highly dependent on the app; sometimes it's appropriate to just replace it, other times you want to do a merge, etc etc etc. Makes it hard to give advice about what to do

Mittchel commented 8 years ago

@paulcbetts Thanks!

For some reason it feels off to AddRange twice inside the Subscribe just to get the Remote data. Isn't there an easier/prettier way to substract the remote data?

And indeed I do agree that in different scenarios you could either choose for replace or merge, but if you replace you might have a re-render of the screen which doesn't really seem that pretty.

Mittchel commented 8 years ago

@paulcbetts One more question if I may!

I am creating something like a 'chat' application. I fetch the messages and cache them and when new messages come in via Pusher (socket) I would like to add this object to the existing cache. So what I did is:

The cached result is accessible on Class level and when a message comes in I just add it to this list. When the View Disappears I invalidate the old cache and create a new one with the new editted list. For some reason when I open the view again it uses the new cache as inspected, but there is a little delay/flickering.

Any idea what this might be or a different way to approach this?

(Xamarin.iOS)