Skip to content
This repository has been archived by the owner on Oct 14, 2023. It is now read-only.

Feat: Network Rule #7

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open

Feat: Network Rule #7

wants to merge 26 commits into from

Conversation

canonall
Copy link

@canonall canonall commented Mar 21, 2023

The network rule requires two parameters: NetworkState and networkTimeoutDuration. The NetworkState parameter specifies the desired state of the network that needs to be satisfied, while the networkTimeoutDuration parameter specifies the duration to observe changes in the network state.

The network rule follows the flow outlined below:

  1. If the network state is not satisfied during networkTimeoutDuration:
    • NetworkRuleTimeout will be emitted.
    • DidCancel will be emitted.
  2. If the network state is satisfied during networkTimeoutDuration:
    • NetworkRuleSatisfied will be emitted and NetworkRuleTimeout@ scope will be cancelled.
    • WillRun will be emitted.
    • If the job is completed within jobTimeout:
      • result will be emitted which can be one of three events: DidSucceed, DidFail, DidFailOnRemove
      • Otherwise JobTimeout and DidCancel will be emitted.

Changes in the code:

  • define network states (WIFI, MOBILE, NONE)
  • observe changes in the network state
  • move withTimeout() to JobQueue
  • add more tests
  • some clean-up

@canonall canonall changed the title Feat: Network Rule Draft: Feat: Network Rule Mar 21, 2023
@canonall canonall requested a review from benjohnde March 21, 2023 08:40
@canonall canonall changed the title Draft: Feat: Network Rule Feat: Network Rule Mar 22, 2023
Copy link
Member

@benjohnde benjohnde left a comment

Choose a reason for hiding this comment

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

I like the implementation overall @canonall, well done!

serializers,
configuration,
store
serializers = serializers,
Copy link
Member

Choose a reason for hiding this comment

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

I like the use of named arguments.


constructor(task: Task, info: JobInfo) : this (UUIDFactory.create(), info, task, Clock.System.now())
constructor(task: Task, info: JobInfo) : this(
UUIDFactory.create(),
Copy link
Member

Choose a reason for hiding this comment

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

Not that important, you could also use named arguments here :)

listener.emit(result)
var shouldRunJob = false
try {
withTimeout(job.info.networkRuleTimeout) NetworkRuleTimeout@{
Copy link
Member

Choose a reason for hiding this comment

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

Just curious why the withTimeout, my understanding is that NetworkListener.currentNetworkState gives instant feedback.

Copy link
Author

Choose a reason for hiding this comment

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

                           withTimeout(job.info.networkRuleTimeout) NetworkRuleTimeout@{
                                networkListener.currentNetworkState.collect { currentNetworkState ->
                                    val isNetworkRuleSatisfied =
                                        networkListener.isNetworkRuleSatisfied(
                                            jobInfo = job.info,
                                            currentNetworkState = currentNetworkState
                                        )
                                    if (isNetworkRuleSatisfied) {
                                        shouldRunJob = true
                                        jobEventListener.emit(JobEvent.NetworkRuleSatisfied(job))
                                        [email protected]("Network rule satisfied")
                                    }
                                }
                            }

The aim of using the withTimeout function was to ensure that the network requirement is met within a specified time frame. With each emission of the currentNetworkState, the 'isNetworkRuleSatisfied' flag is checked, instantly. If the flag is true, the NetworkRuleTimeout scope is canceled. If the flag is false, the currentNetworkState continues to be observed until the timeout expires. Without withTimeout the job would never be canceled until the network requirement is met, which I thought not desirable.

networkManager
.observeNetworkConnection()
.collect { currentNetworkState ->
_currentNetworkState.emit(
Copy link
Member

Choose a reason for hiding this comment

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

If I did not misunderstand your implementation, you could also dismiss the when block and directly write:

_currentNetworkState.emit(currentNetworkState ?: NetworkState.NONE)

networkManager.startMonitoring()
scope.launch {
networkManager.network.collectLatest { currentNetworkState ->
_currentNetworkState.emit(
Copy link
Member

Choose a reason for hiding this comment

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

If I did not misunderstand your implementation, you could also dismiss the when block and directly write:

_currentNetworkState.emit(currentNetworkState)

Copy link
Member

Choose a reason for hiding this comment

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

Small note: in this case you could simplify it with (same applies to the android side):

_currentNetworkState.emit(it)

Copy link
Author

Choose a reason for hiding this comment

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

Actually, I don't like using it. I choose the long names most of the time 🙃


private val connectionPublisher = MutableStateFlow(getCurrentNetworkConnection())

fun observeNetworkConnection(): Flow<NetworkState?> = connectionPublisher
Copy link
Member

Choose a reason for hiding this comment

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

Do I understand the correctly that on iOS we basically observe the connection, hence the Flow could emit changes and on Android we only get the state at the time of the function invocation?

Copy link
Author

Choose a reason for hiding this comment

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

Well, I just realized that now and I guess the answer is yes. I will do the following change:

Suggested change
fun observeNetworkConnection(): Flow<NetworkState?> = connectionPublisher
val network: Flow<NetworkState?> = connectionPublisher

and then listen it just as in IOS in Android NetworkListener

override fun observeNetworkState() {
        scope.launch {
            networkManager
                .network
                .collectLatest { currentNetworkState ->
                    _currentNetworkState.emit(currentNetworkState ?: NetworkState.NONE)
                }
        }
    }



// not needed for Android
override fun stopMonitoring() {}
Copy link
Member

Choose a reason for hiding this comment

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

We could either implement that in a way that the state gets refreshed every 5 seconds (or in any other interval) to mimic the iOS behaviour a bit. But this is not subject to be implemented instantly but rather - if I understood everything right - to keep it as an issue for future improvements.

Copy link
Author

Choose a reason for hiding this comment

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

Actually, I can just create a job variable and assign it in observeNetworkState. Then, I can simply cancel it. I will add that for now. I had a wrong thinking at first. I will also do the same for ios.

Suggested change
override fun stopMonitoring() {}
override fun stopMonitoring() {
job?.cancel()
}

preAndroidMNetworkConnection(connectivityManager)
}

@TargetApi(Build.VERSION_CODES.M)
Copy link
Member

Choose a reason for hiding this comment

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

Lovely to have it backwards compatible. We should internally discuss which API level makes sense for Flowify. I would rather start with Android 6 or 7 to get rid of some old chunks. But nice that you've implemented it with old versions in mind! Just leave it as is at the moment, we may remove it later on!

@@ -135,18 +145,49 @@ abstract class AbstractJobQueue(
job.delegate = delegate
Copy link
Member

Choose a reason for hiding this comment

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

I guess somewhere in the while(isActive) loop we need to invoke NetworkListener.observeNetworkState(). The invocation is currently missing in the code :)

Copy link
Author

Choose a reason for hiding this comment

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

🥴 sir yes sir

}

queue.schedule(TestData(id), ::TestTask) {
minRequiredNetwork(NetworkState.NONE, 3.seconds)
Copy link
Author

Choose a reason for hiding this comment

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

As there is no real or simulated device to observe the network, the default value for the network is always NetworkState.NONE. If the minRequiredNetwork is also set to NetworkState.NONE, the test will pass. However, in any other case, the test will fail. Any ideas to deal with this?

Copy link
Author

Choose a reason for hiding this comment

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

I think that was because I did not call observeNetworkState as you mentioned. Now, it behaves normally. The test environment network is set to MOBILE I guess. But I do not know how to change it to WIFI if I need to.

listener.emit(result)
var shouldRunJob = false
try {
withTimeout(job.info.networkRuleTimeout) NetworkRuleTimeout@{
Copy link
Author

Choose a reason for hiding this comment

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

                           withTimeout(job.info.networkRuleTimeout) NetworkRuleTimeout@{
                                networkListener.currentNetworkState.collect { currentNetworkState ->
                                    val isNetworkRuleSatisfied =
                                        networkListener.isNetworkRuleSatisfied(
                                            jobInfo = job.info,
                                            currentNetworkState = currentNetworkState
                                        )
                                    if (isNetworkRuleSatisfied) {
                                        shouldRunJob = true
                                        jobEventListener.emit(JobEvent.NetworkRuleSatisfied(job))
                                        [email protected]("Network rule satisfied")
                                    }
                                }
                            }

The aim of using the withTimeout function was to ensure that the network requirement is met within a specified time frame. With each emission of the currentNetworkState, the 'isNetworkRuleSatisfied' flag is checked, instantly. If the flag is true, the NetworkRuleTimeout scope is canceled. If the flag is false, the currentNetworkState continues to be observed until the timeout expires. Without withTimeout the job would never be canceled until the network requirement is met, which I thought not desirable.

networkManager.startMonitoring()
scope.launch {
networkManager.network.collectLatest { currentNetworkState ->
_currentNetworkState.emit(
Copy link
Author

Choose a reason for hiding this comment

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

Actually, I don't like using it. I choose the long names most of the time 🙃


private val connectionPublisher = MutableStateFlow(getCurrentNetworkConnection())

fun observeNetworkConnection(): Flow<NetworkState?> = connectionPublisher
Copy link
Author

Choose a reason for hiding this comment

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

Well, I just realized that now and I guess the answer is yes. I will do the following change:

Suggested change
fun observeNetworkConnection(): Flow<NetworkState?> = connectionPublisher
val network: Flow<NetworkState?> = connectionPublisher

and then listen it just as in IOS in Android NetworkListener

override fun observeNetworkState() {
        scope.launch {
            networkManager
                .network
                .collectLatest { currentNetworkState ->
                    _currentNetworkState.emit(currentNetworkState ?: NetworkState.NONE)
                }
        }
    }

@@ -135,18 +145,49 @@ abstract class AbstractJobQueue(
job.delegate = delegate
Copy link
Author

Choose a reason for hiding this comment

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

🥴 sir yes sir



// not needed for Android
override fun stopMonitoring() {}
Copy link
Author

Choose a reason for hiding this comment

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

Actually, I can just create a job variable and assign it in observeNetworkState. Then, I can simply cancel it. I will add that for now. I had a wrong thinking at first. I will also do the same for ios.

Suggested change
override fun stopMonitoring() {}
override fun stopMonitoring() {
job?.cancel()
}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants