Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Fix/129 fix eliminate parkings flickering #152

Merged
merged 15 commits into from
Aug 12, 2024

Conversation

mikolaj-jalocha
Copy link
Member

I've tested it on Android device (the bug was there before as well). Everything looks fine. Hopefully new state is being updated.

@mikolaj-jalocha mikolaj-jalocha added the bug Something isn't working label Aug 10, 2024
@mikolaj-jalocha mikolaj-jalocha self-assigned this Aug 10, 2024
@mikolaj-jalocha mikolaj-jalocha linked an issue Aug 10, 2024 that may be closed by this pull request
Copy link
Member

@simon-the-shark simon-the-shark left a comment

Choose a reason for hiding this comment

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

I'll try to do this too when I have a moment. I have few ideas but we'll see how they'll do

@@ -13,7 +13,7 @@ extension RefIntervalRefreshX on Ref {
void setRefresh(Duration interval) {
final timer = Timer.periodic(
interval,
(t) => invalidateSelf(),
(t) => invalidate(parkingsRepositoryProvider),
Copy link
Member

Choose a reason for hiding this comment

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

Unfortunetly this throws :(

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: 'package:riverpod/src/framework/element.dart': Failed assertion: line 614 pos 11: 'listenable._origin != origin': A provider cannot depend on itself

basically invalidateSelf() and invalidate(someProvider) works the same with the difference the second one seems to throw when use inside the same provider (I didn't knew that actually, but make sense)

@simon-the-shark
Copy link
Member

@mikolaj-jalocha I've been playing around with it for some time 😭 But I have a fix. Suprisingly it wasn't so much about the riverpod but more with the UI itself and the switch pattern matching logic. Tell me what think. Let's hope everything works as expected now

Nagranie.z.ekranu.2024-08-10.o.16.14.49.mov

@simon-the-shark simon-the-shark marked this pull request as draft August 10, 2024 14:28
@simon-the-shark simon-the-shark marked this pull request as ready for review August 10, 2024 14:28
@simon-the-shark simon-the-shark force-pushed the fix/129-fix-eliminate-parkings-flickering branch from 1a93f8c to c0d5083 Compare August 10, 2024 17:42
@simon-the-shark
Copy link
Member

simon-the-shark commented Aug 10, 2024

@mikolaj-jalocha when you have a moment you can make a review here, cause i cannot request review from its author

@mikolaj-jalocha
Copy link
Member Author

@simon-the-shark thanks for feedback. I didn't see exception before (I should look on logs..).

I will do review ASAP (tomorrow)

Copy link
Member Author

@mikolaj-jalocha mikolaj-jalocha left a comment

Choose a reason for hiding this comment

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

The flickering issue seemed simple to fix but ended up requiring quite a refactor. Good job! I wouldn’t have come up with a solution so quickly—my Flutter experience is too limited and my state management skills aren’t strong enough for that. 😂

Below are a few suggestions and some questions I have about how certain parts of the code work, so I can better understand the issue.

I really like the introduction of IList. It's an order of magnitude faster than Flutter's built-in version, and I think it could have a noticeable impact on our app’s performance.
By the way, every time I review your code, I end up reading documentation on something new—which is cool!

@@ -6,7 +6,7 @@ import "../../../config/api_base_config.dart";
part "iparking_client.g.dart";

abstract class ParkingsConfig {
static const parkingsRefreshInterval = Duration(minutes: 1);
static const parkingsRefreshInterval = Duration(seconds: 30);
Copy link
Member Author

Choose a reason for hiding this comment

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

Is the period here ok?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah I did this on purpose. It's not our server anyway and previously if we entered app in unfortunate moment we could have almost 1 minut delay from the server. Now it's tops 30 sec. In the future we can think of some mechanism that will "align" our refreshes with those on server

.watch(context.mapSourceRepository<T>())
.value
.whereNonNull
.toList();
Copy link
Member Author

Choose a reason for hiding this comment

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

I wonder if we can use here immutable list? (IList)

btw the way Consumer here's introduced improved visibility a lot imo.

Copy link
Member Author

Choose a reason for hiding this comment

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

Btw, is this a change that reduced flickering? Moving state refreshment inside AsyncValue?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah we can use IList here

I moved the state listening down the tree in few places, meaning less widgets are refreshed needlesly. But it still doesn't help :P with the flickering

AsyncError(:final error) =>
SliverToBoxAdapter(child: MyErrorWidget(error)),
AsyncValue(:final value) =>
_DataSliverList<T>(value.whereNonNull.toList())
_ => const DataListLoading(),
Copy link
Member Author

Choose a reason for hiding this comment

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

What happened to AsyncLoading?

Copy link
Member

Choose a reason for hiding this comment

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

That's the change that helped. This alone solves the problem. I got the idea from docs as well, from the pull to refresh guide. https://riverpod.dev/docs/case_studies/pull_to_refresh

What was the problem is the fact that after refresh the state changes to AsyncLoading and we showed the loading screen for a second.

It turns out through, even though the state is loading. State.value still holds previous value for us to use.

So I changed the pattern matching switch logic a bit.

Now if the async value have any value we show it. If it has error we show error and in all other cases (_ means all unmatched previously things) we show loading screen. So the loading screen shows if we have async loading or async data with value=null

Comment on lines +20 to +23
// misz-masz here fixed flickering
// we probably should consider similar refactoring to all switches, but won't be visible in our other use-cases
AsyncValue(:final IList<T?> value) =>
_DataSliverList<T>(value.whereNonNull.toIList()),
Copy link
Member Author

Choose a reason for hiding this comment

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

Does the introduction of immutable list reduced flickering?

Copy link
Member

Choose a reason for hiding this comment

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

It was one of many things I've tried cause IList implements list comparability, meaning ILists are equal if have equal elements in same order. Hovewer I forgot the riverpod doesn't check equality == operator anyway, but uses identical comparator. More you can read here in one of the questions https://riverpod.dev/docs/essentials/faq

It allowed me to reduce flickering frequency with manual check, but didn't eliminate it fully:

if (state != newstate) {
state = newstate; // or ref.invalidate or smth, basically reduces flickering to only situation when the number of places changed
} 

So its not the solution but I left the IList anyway cause it's recommended to have immutable and combarable state objects

final itemSelected = ref.watch(mapControllers.activeMarker);
if (itemSelected != null) {
return [itemSelected]; // shows only selected building
return [itemSelected].lock; // shows only selected building
Copy link
Member Author

Choose a reason for hiding this comment

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

Tell me if i properly understand that; if the item is selected, the list cannot be modified?

Copy link
Member

Choose a reason for hiding this comment

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

I believe .lock is just a shortcut for .toIList() but I didn't dive deeply

@simon-the-shark
Copy link
Member

simon-the-shark commented Aug 12, 2024

Thanks for the review! Hopefully i answered your questions here. If not, then hit me up or we can hop on a call too.

Basically for state management i reccomend the official riverpod.dev docs. They are crazy good. I like to read all the pages there from time to time when I have a problem.

And also if youre not that familiar with pattern matching concept you can check it out too. Its very powerfull tool, pretty new to dart and we're using it in a very limited range here

@simon-the-shark simon-the-shark merged commit 0fa9d5c into main Aug 12, 2024
2 checks passed
@simon-the-shark simon-the-shark deleted the fix/129-fix-eliminate-parkings-flickering branch August 12, 2024 17:41
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
bug Something isn't working
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

fix/ eliminate parkings flickering
2 participants