Skip to content

Conversation

@eyalgu
Copy link
Contributor

@eyalgu eyalgu commented Feb 29, 2020

Non-working PR to show the proposed way of adding support for allowing the fetcher to communicate errors not via exceptions.

The main complication here is in the builder API. we already have 2 builders because we support a builder with and without source of truth (to change to generic type). If we went this way we would either need to support 4 builder or we will need to change the builder API to force providing the source of truth (and the new translator) in the builder's constructor

@eyalgu eyalgu requested a review from yigit February 29, 2020 23:27
*/
fun disableCache(): StoreBuilder<Key, Output>

fun<UnwrappedOutput> fetcherTransformer(fetcherTransformer: (Output) -> FetcherResult<UnwrappedOutput>)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ignore anything in this file aside from this line. Everything bellow is a mess that I didn't want to get into before we decide this is the way to go.

I think the only scalable way to add support to another generic type changing parameter (like source of truth) would be for change the builder API it only receive the source of truth in the constructor.

Aside from builder changes (and tests) all the other changes needed to support this change are in the PR (and are pretty minimal)

Copy link
Collaborator

@yigit yigit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a quick look from the phone.

origin = ResponseOrigin.Fetcher
)
}
}.catch {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in this new world, it should crash the whole flow and hopefully the app

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is only intended to allow the use of a wider variety of fetchers. Why do you think we should change the behavior of Store? In general we don't propagate exceptions but concert then to errors, why would this change affect this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because in the previous API, they can only return values hence we have to catch exceptions for errors.

In this new world, they get the ability to return errors so any exception can be considered exceptional. FYI this is also what paging does but we are considering options to maybe catch IO Exceptions by default to be more pragmatic.

A reason to opt into this new API would be to catch errors like NPE's and if we eat them, it would probably make people unhappy.

*/
fun disableCache(): StoreBuilder<Key, Output>

fun<UnwrappedOutput: Any> fetcherTransformer(fetcherTransformer: (Output) -> FetcherResult<UnwrappedOutput>)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would store response work here? Or is it too generic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Store response has an origin, which I don't want to let users override.

@eyalgu eyalgu requested review from digitalbuddha and yigit March 11, 2020 06:13
@eyalgu
Copy link
Contributor Author

eyalgu commented Mar 11, 2020

Sorry this was open for so long. finally got around to finish it. Note that this would be a major API change for how we build store (but no change to Store's API itself).

@eyalgu
Copy link
Contributor Author

eyalgu commented Mar 11, 2020

the tl;dr of the api change is that you have to pass a source of truth when you create a builder rather than later:

        StoreBuilder
            .fromNonFlow<Pair<String, RedditConfig>, List<Post>, List<Post>>(
                fetcher = { (query, config) ->
                    provideRetrofit().fetchSubreddit(query, config.limit)
                        .data.children.map(::toPosts)
                },
                sourceOfTruth = SourceOfTruth.from(
                    reader = { (query, _) -> db.postDao().loadPosts(query) },
                    writer = { (query, _), posts -> db.postDao().insertPosts(query, posts) },
                    delete = { (query, _) -> db.postDao().clearFeedBySubredditName(query) },
                    deleteAll = db.postDao()::clearAllFeeds
                )
            )
            .build()

@codecov
Copy link

codecov bot commented Mar 11, 2020

Codecov Report

❗ No coverage uploaded for pull request base (master@fc8da86). Click here to learn what that means.
The diff coverage is 64.92%.

Impacted file tree graph

@@            Coverage Diff            @@
##             master     #123   +/-   ##
=========================================
  Coverage          ?   83.04%           
  Complexity        ?      229           
=========================================
  Files             ?       48           
  Lines             ?      973           
  Branches          ?      145           
=========================================
  Hits              ?      808           
  Misses            ?      109           
  Partials          ?       56           
Impacted Files Coverage Δ Complexity Δ
...2/src/main/kotlin/com/dropbox/store/rx2/RxStore.kt 100.00% <ø> (ø) 0.00 <0.00> (?)
.../dropbox/android/external/store4/impl/RealStore.kt 90.90% <ø> (ø) 14.00 <0.00> (?)
...d/external/store4/impl/SourceOfTruthWithBarrier.kt 93.54% <ø> (ø) 6.00 <0.00> (?)
...ain/kotlin/com/dropbox/store/rx2/RxStoreBuilder.kt 16.66% <16.66%> (ø) 0.00 <0.00> (?)
...m/dropbox/android/external/store4/SourceOfTruth.kt 65.21% <65.21%> (ø) 0.00 <0.00> (?)
...in/kotlin/com/dropbox/store/rx2/RxSourceOfTruth.kt 70.00% <70.00%> (ø) 0.00 <0.00> (?)
...om/dropbox/android/external/store4/StoreBuilder.kt 75.00% <72.22%> (ø) 0.00 <0.00> (?)
...m/dropbox/android/external/store4/StoreResponse.kt 91.42% <87.50%> (ø) 12.00 <3.00> (?)
.../android/external/store4/impl/FetcherController.kt 100.00% <100.00%> (ø) 6.00 <0.00> (?)
.../android/external/store4/impl/RealSourceOfTruth.kt 66.66% <100.00%> (ø) 0.00 <0.00> (?)
... and 48 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update fc8da86...e883e5d. Read the comment docs.

@codecov
Copy link

codecov bot commented Mar 11, 2020

Codecov Report

Merging #123 into master will decrease coverage by 0.70%.
The diff coverage is 60.00%.

Impacted file tree graph

@@             Coverage Diff              @@
##             master     #123      +/-   ##
============================================
- Coverage     84.19%   83.49%   -0.71%     
  Complexity      242      242              
============================================
  Files            45       45              
  Lines           949      957       +8     
  Branches        151      147       -4     
============================================
  Hits            799      799              
- Misses           95      104       +9     
+ Partials         55       54       -1     
Impacted Files Coverage Δ Complexity Δ
.../android/external/store4/impl/FetcherController.kt 100.00% <ø> (ø) 6.00 <0.00> (ø)
.../dropbox/android/external/store4/impl/RealStore.kt 90.90% <ø> (ø) 14.00 <0.00> (ø)
...m/dropbox/android/external/store4/StoreResponse.kt 95.00% <50.00%> (-5.00%) 13.00 <0.00> (ø)
...om/dropbox/android/external/store4/StoreBuilder.kt 59.61% <52.77%> (-6.59%) 0.00 <0.00> (ø)
...pbox/android/external/store4/impl/SourceOfTruth.kt 64.10% <65.21%> (+1.60%) 0.00 <0.00> (ø)
...2/src/main/kotlin/com/dropbox/store/rx2/RxStore.kt 72.41% <100.00%> (-5.37%) 0.00 <0.00> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 38baeb7...9e3801e. Read the comment docs.

@eyalgu
Copy link
Contributor Author

eyalgu commented Mar 11, 2020

(still need to add a couple of tests here, will add soon hopefully)

@eyalgu
Copy link
Contributor Author

eyalgu commented Mar 18, 2020

@yigit @digitalbuddha this is now ready for review - it looks bigger than it is because of the changes to the builder's interface (and exposing sourceOfTruth through the interface)

@yigit
Copy link
Collaborator

yigit commented Mar 22, 2020

Sorry i didn't have time to check this, planning to do it now.
i did a quick through and i have a question.

Does it make sense to also make the fetcher a class instead of a function?

That could help w/ two things:
a) we can have future changes there w/o going into this builder API issues
b) different ways to create a Fetcher would be isolated in the Fetcher itself, instead of leaking into the store builder API.

I just wanted to ask in case you've already evaluated that option.

Copy link
Collaborator

@yigit yigit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not rally super happy with this change. i think it creates a lot of API load.

I'll try to prototype the Fetcher as a class idea where we can make it something that always retturns value or error and then provide a wrapper that returns the value type which we will catch exceptions.
I would like to know if you've already tried that though.

val adapter = moshi.adapter<RedditConfig>(RedditConfig::class.java)
return StoreBuilder
.fromNonFlow<Unit, RedditConfig> {
.fromNonFlow<Unit, RedditConfig, RedditConfig> (fetcher = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was something we tried to avoid in the new store builder. is it required now?

build.gradle Outdated

ext.versions = [
androidGradlePlugin : '4.0.0-beta01',
androidGradlePlugin : '3.6.0',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not important but probably better to use 3.6.1 ?

* WARNING: Delete operation is not supported when using a legacy [com.dropbox.android.external.store4.Persister],
* please use another override
*/
fun <Key, Output> fromLegacyPresister(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we mark this deprecated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or delete it, we're still in alpha

*/
fun <Key : Any, RawOutput : Any, Output : Any> fromNonFlow(
fetcher: suspend (key: Key) -> RawOutput,
fetcherTransformer: (RawOutput) -> FetcherResult<Output>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a bit weird., why cant it just provide a FetcherResponse? Name conflict?

/**
* Creates a new [StoreBuilder] from a [Flow] fetcher.
*
* Use when creating a [Store] that fetches objects in an websocket-like multiple responses
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

websocket seems weird example as people may not be able to relate to it (i might be wrong). Maybe we can use firebase as an example instead?

origin = ResponseOrigin.Fetcher
)
}
}.catch {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because in the previous API, they can only return values hence we have to catch exceptions for errors.

In this new world, they get the ability to return errors so any exception can be considered exceptional. FYI this is also what paging does but we are considering options to maybe catch IO Exceptions by default to be more pragmatic.

A reason to opt into this new API would be to catch errors like NPE's and if we eat them, it would probably make people unhappy.

@yigit
Copy link
Collaborator

yigit commented Mar 23, 2020

created #139 to make discussions easier. unlike this one, it is not a complete PR.

@eyalgu
Copy link
Contributor Author

eyalgu commented Mar 30, 2020

Sorry for dropping off here. It's been hard to find OSS time in the last few weeks with the shelter in place and all. #139 seems good to me. I put a comment around the fetcher's API.

I'll try to integrate those changes into this revision over the next week of so, but feel free to finish up #139 you feel like it and if I'm taking too long.

Copy link
Collaborator

@yigit yigit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think overall is good, thank you. just small comments.

@eyalgu
Copy link
Contributor Author

eyalgu commented Apr 23, 2020

@yigit Thanks for all the great feedback. This should be ready to go now.

@eyalgu eyalgu requested a review from yigit April 23, 2020 16:43
Copy link
Collaborator

@yigit yigit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, just small nits.
thanks!

*/
interface SourceOfTruth<Key, Input, Output> {
val defaultOrigin: ResponseOrigin
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
val defaultOrigin: ResponseOrigin
/**
* The data origin value which will be reported in [StoreResponse.origin]. For instance, if the SourceOfTruth keeps
* the data on disk, this value should be [ResponseOrigin.Persister].
*/
val defaultOrigin: ResponseOrigin

Comment on lines 29 to 30
override val defaultOrigin =
ResponseOrigin.Persister
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
override val defaultOrigin =
ResponseOrigin.Persister
override val defaultOrigin = ResponseOrigin.Persister

Comment on lines 51 to 52
override val defaultOrigin =
ResponseOrigin.Persister
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
override val defaultOrigin =
ResponseOrigin.Persister
override val defaultOrigin = ResponseOrigin.Persister

yigit
yigit previously approved these changes Apr 28, 2020
Copy link
Collaborator

@yigit yigit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a small nit, lgtm; Thanks!

yigit
yigit previously approved these changes Apr 29, 2020
@eyalgu
Copy link
Contributor Author

eyalgu commented Apr 29, 2020

reverting #159 for now because it makes it impossible to update both the source and the sample in the same diff. Let's discuss bringing it back in an issue. there are pros and cons for it, but for now, while the API is unstable it's just to block us. FYI @DYJParker

@DYJParker
Copy link
Contributor

Just tagging @digitalbuddha in this, since he drove the change.

reverting #159 for now because it makes it impossible to update both the source and the sample in the same diff. Let's discuss bringing it back in an issue. there are pros and cons for it, but for now, while the API is unstable it's just to block us. FYI @DYJParker

@eyalgu
Copy link
Contributor Author

eyalgu commented Apr 29, 2020

opened #161 to continue the discussion over #159

@eyalgu eyalgu merged commit e71a4e9 into master Apr 29, 2020
@eyalgu eyalgu linked an issue Apr 29, 2020 that may be closed by this pull request
@eyalgu eyalgu deleted the errors branch April 29, 2020 18:49
itsandreramon pushed a commit to itsandreramon/Store that referenced this pull request Feb 26, 2025
* Support non exception errors from fetcher

* revert parital changes to store builder to reduce noise

* finish off diff

* Allow to create a FetcherResult.Error without a Throwable. Add tests

* Add missing funcion and more tests

* lint

* unflake RxFlowableStoreTest

* try to rename FakeFetcher to FakeRxFetcher to (maybe) solve missing codcov

* move SourceOfTruth out of impl package

* Rename accidental change of RxStoreBuilder.fromMaybe back to formSingle

* Introduce Fetcher from MobileNativeFoundation#139

* fix Rx artifact

* delete legacy presistor factory

* fix api file

* move fetcher to be a typealias

* code review comments + clean up documentation

* code review comments

* Update store/src/main/java/com/dropbox/android/external/store4/Fetcher.kt

Co-Authored-By: Yigit Boyar <[email protected]>

* Revert "Update sample app's build.gradle to refer to the externally released version of Store (MobileNativeFoundation#159)"

This reverts commit fc8da86.

* update releasing.md

Co-authored-by: Yigit Boyar <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

When should we have the sample app point to released artifacts? Handle fetchers that don't throw an exception

5 participants