Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add ContainerNode and RiverpodNode to devtool #1471

Closed

Conversation

adsonpleal
Copy link

This PR adds support for Flutter DevTools by improving the already present file devtool.dart.

With the changes present here we can more easily get the data needed to present the provider state on Flutter DevTools.

For more information check the discussion here.

The UI that these changes enable is the following:

final.experience.mp4

Soon I will open a PR on devtools with the addition of RiverpodScreen.

@rrousselGit
Copy link
Owner

👋
Could you add tests for all of this? 🙏

@adsonpleal
Copy link
Author

👋 Could you add tests for all of this? 🙏

yes! Will do!

}

extension on ContainerNode {
bool isEqualTo(ContainerNode other) {
Copy link
Author

Choose a reason for hiding this comment

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

we could use freezed or equatable on the actual classes, but I noticed that riverpod package doesn't depend on these packages.

@@ -1,7 +1,50 @@
// ignore_for_file: public_member_api_docs
// ignore_for_file: public_member_api_docs, invalid_use_of_visible_for_testing_member
Copy link
Owner

Choose a reason for hiding this comment

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

What is this change for?

Copy link
Author

Choose a reason for hiding this comment

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

this is related to the other question that you made #1471 (comment) . I can totally remove it if I make it a part of provider_base.dart.

Copy link
Author

Choose a reason for hiding this comment

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

removed

@@ -70,13 +117,26 @@ class RiverpodBinding {
return binding!;
}


bool get supportsDevTool => true;
Copy link
Owner

Choose a reason for hiding this comment

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

What is the purpose of this getter?

Copy link
Author

Choose a reason for hiding this comment

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

This was just to show or not the Riverpod tab on devtools. But I think I might have a better way of doing that. We could just use a try-catch instead. You can see the implementation here https://github.com/flutter/devtools/pull/4210/files#diff-90638f67ff3222b19bb967f40f76a108841c7a1b6917d8f853d8a04c55d0bb9eR9 .

Copy link
Owner

Choose a reason for hiding this comment

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

What about making this the package version?
We'll want to expose the version anyway to know if a feature newly added devtool features are supported or not in the future.

This would of course need a unit test to check that the version is in sync with the pubspec.yaml

Copy link
Author

Choose a reason for hiding this comment

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

yes, I tried adding the version, but couldn't find a consistent way of getting if from the pubspec. But now that you mentioned the test we could just have a test that reads the pubspec and compare the versions. 🤔

Is this OK for you? We will need to also add some documentation for it, I suppose.

Copy link
Author

Choose a reason for hiding this comment

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

I was thinking here, instead of using the actual package version, why not create a "virtual" versioning for devtools support? Like we could call something like "devtoolsSupportVersion" and start it at 1, then we just increment the number every time we add a new feature. This way we don't need to always change the version when riverpod version changes, we would only need to change when we change the devtool implementation.

WDYT?

final String id;
final String containerId;
final String? name;
final Result<dynamic>? state;
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
final Result<dynamic>? state;
final Result<Object?>? state;

Copy link
Author

Choose a reason for hiding this comment

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

Perfect, I'll make the change.

Copy link
Author

Choose a reason for hiding this comment

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

I can't change it to Object , the element.getState() returns a dynamic, since it is a generic.

@@ -202,6 +202,8 @@ class _ProviderListener<State> implements ProviderSubscription<State> {
State read() => listenedElement.readSelf();
}

var _elementDebugNextId = 0;
Copy link
Owner

Choose a reason for hiding this comment

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

Move this as a static variable of ProviderElementBase

Copy link
Author

Choose a reason for hiding this comment

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

Done, just wondering if there is a reason other than keeping the consistency. In the end won't it be the same?

///
/// Should not be used, only meant for debug purposes
@visibleForTesting
bool get dependencyMayHaveChanged => _dependencyMayHaveChanged;
Copy link
Owner

Choose a reason for hiding this comment

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

I don't want this to be visible. If necessary, make the devtool file in the same part as this file

Copy link
Author

Choose a reason for hiding this comment

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

I'll try making it a part, I tried going with part at first but had some issues. I'll try again and let you know.

Copy link
Author

Choose a reason for hiding this comment

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

I've removed the public getter.

/// and [_onRemoveListener]
void _notifyListenersChanged() {
assert(() {
RiverpodBinding.debugInstance.providerChanged(
Copy link
Owner

Choose a reason for hiding this comment

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

Why do we notify that a provider has changed when we add/remove listeners?

Copy link
Author

Choose a reason for hiding this comment

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

to be able to show the alert saying that the provider might be outdated. Like refreshing the list when this event happens.

We could just remove it, it would make the implementation smaller on both sides, but the displayed data would be less realtime.

Maybe adding a "refresh all containers" (I mean only on the devtools side, not refreshing the providers states) button would be better, this way we could refresh the devtools data. WDYT?

Copy link
Owner

Choose a reason for hiding this comment

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

But why "providerChanged"? I would expect a new event for this.
We can create as many events as we want. No need to reuse the same one for multiple things

Copy link
Author

Choose a reason for hiding this comment

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

oh! Got it! Well, in the devtools implementation they would do the same thing, which is to refresh the provider state. That is why I used the same event, but I could change it to something like 'riverpod:provider_listeners_changed' and listen to both events there on devtools.

});

test('should add container to containers map', () {
final firstContainer = ProviderContainer();
Copy link
Owner

Choose a reason for hiding this comment

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

use the createContainer utility instead of manually creating ProviderContainers

Copy link
Author

Choose a reason for hiding this comment

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

cool! I'll change that.

Copy link
Author

Choose a reason for hiding this comment

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

done


expect(
containerNodes,
predicate(
Copy link
Owner

Choose a reason for hiding this comment

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

What is the purpose of the predicate here?

Copy link
Author

@adsonpleal adsonpleal Jul 5, 2022

Choose a reason for hiding this comment

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

well, this is a creative solution to avoid adding Equatable or Freezed to the package.

As I can't compare two objects directly I created the extension functions to make the fields check without the need of changing production code. It is not the prettiest code, I know. Do you have any suggestions?

Copy link
Owner

Choose a reason for hiding this comment

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

Feel free to override ==/hashCode for those classes.

Copy link
Author

Choose a reason for hiding this comment

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

perfect! This will be way cleaner hehe

Copy link
Author

Choose a reason for hiding this comment

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

added here 3fe5095

@rrousselGit
Copy link
Owner

I haven't forgotten about this. I'll continue the work on my own during August.

@adsonpleal
Copy link
Author

I haven't forgotten about this. I'll continue the work on my own during August.

cool, so you mean you'll continue this work? I can still work on it if you want, I was just waiting for you comments.

Let me know if you need my help.

Cheers!

@rrousselGit
Copy link
Owner

It'd be great if you could add tests on the devtool PR (not this one, the flutter/devtool repo)

@adsonpleal
Copy link
Author

It'd be great if you could add tests on the devtool PR (not this one, the flutter/devtool repo)

For sure! I was just waiting to have this API ready to go. I'll add the tests this week.

@rrousselGit
Copy link
Owner

Don't worry about this api. I don't mind much what it looks like, since it's not public.

What matters is that everything is tested

@adsonpleal
Copy link
Author

@rrousselGit this PR should be ready to be merged, we need to merge it first and create a release since devtools depends on this.

@rrousselGit
Copy link
Owner

Don't worry about this.

For reference, I don't plan on merging this until the devtool PR is ready to merge. These two PRs are intertined. A problem in one would require a change in the other, so they likely need to be merged at the same time.

@adsonpleal
Copy link
Author

Makes sense!

@rrousselGit
Copy link
Owner

rrousselGit commented Aug 13, 2022

This is looking great!

A small note about the sidebar listing providers:

  • I believe that if the provider specifies a name, showing its type is redundant. Especially since we see its type once we select the provider (in the title of the right part)

  • I think it'd be great if providers coming from a family could list the argument

More specifically, I could see the rendering of family involve two bits.

Assuming we have:

final myProvider = Provider.family<String, int>((ref, id) => 'Hello $id');

...

ref.watch(myProvider(42));

Then I think the rendering could be:


 Containers.       |.   Exposed value
----------------------------------------
myProvider(42)     |   family arg:
                   |   42
                   |. -------- 
                   |. exposed value:
                   |. 'Hello 42'

Also, for the sake of readability, I think that if there's only one root ProviderContainer, we don't need to show it in the list. We could show the root provider directly.

This would remove one level of indentation on the sidebar

If there are multiple roots, we'd need to keep the rendering as it is. But that's fairly rare I think

@adsonpleal
Copy link
Author

really great insights! I'll work on them! They should be easy to implement, I'll work on an UI and share with you soon.

@adsonpleal
Copy link
Author

Kapture.2022-08-15.at.11.02.34.mp4

I've made some modifications, WDYT?

What I've changed:

1 - Don't show the container label if there is only one container.
2 - Changed the providers list header to show Providers if there is only one container, otherwise it shows Containers.
3 - Changed how the provider name is rendered, now if the provider is a family provider it also show the argument. Another changes was to remove the type if the provider is named.
4 - Also show the selected provider family argument on the right panel, only if there is one.

@rrousselGit
Copy link
Owner

Nice! That looks great!

Quick note, what is that "StringValue"? Is it a custom class you made as an example? Or is that the object vm_service gives you?
If that's the latter, I'd expect to see the raw string directly.

Also, we'd need to make sure that there's a text wrap if the family arg toString is too long :)

@rrousselGit
Copy link
Owner

Acutally, the video showcases that. Ignore me!

That's awesome, nice work!

@rrousselGit
Copy link
Owner

Quick note: Family args shouldn't be editable. Is that the case here?

@adsonpleal
Copy link
Author

About the family not being editable:

They are, since I'm using the same viewer as the state. I just tested here and you can just edit a value if it is not final 😞.

I was looking at the instance_viewer that you've made for provider (which is the same that I'm using) and there is no option to disable editing. I could add a flag to the InstanceViewer widget like disableEditing, which defaults to false, this way we won't touch other provider implementations.

WDYT?

@adsonpleal
Copy link
Author

And about text wrap:

image

just added a way to wrap if there is no room for the name.

@rrousselGit
Copy link
Owner

Sorry I used the wrong word. Rather than text wrap, I think clipping it is better. As in doing myVeryLongProvid...

The sidebar can be expanded anyway, and the provider can be selected to see it in full.
I think it's more important to see the full list

Especcially considering objects could override toString and end-up with a very long provider name.

@rrousselGit
Copy link
Owner

I was looking at the instance_viewer that you've made for provider (which is the same that I'm using) and there is no option to disable editing. I could add a flag to the InstanceViewer widget like disableEditing, which defaults to false, this way we won't touch other provider implementations.

WDYT?

SGTM

@adsonpleal
Copy link
Author

image

changed

@rrousselGit
Copy link
Owner

Epic

A nit point would be to rename 'container" into providerContainer to be more specific. But otherwise LGTM

@adsonpleal
Copy link
Author

Epic

A nit point would be to rename 'container" into providerContainer to be more specific. But otherwise LGTM

You mean written like ProviderContainer, just like the class?

@adsonpleal
Copy link
Author

And what about the header? Should we keep Containers or change to ProviderContainers ?

@rrousselGit
Copy link
Owner

You mean written like ProviderContainer, just like the class?

Yes

And what about the header? Should we keep Containers or change to ProviderContainers ?

The header too.

@adsonpleal
Copy link
Author

image

Done, now I just need to work on the editable flag for InstanceViewer.

@rrousselGit
Copy link
Owner

Perfect. Awesome work

@adsonpleal
Copy link
Author

done flutter/devtools@392f6b9, let me know if you think anything can be improved.

@rrousselGit
Copy link
Owner

@adsonpleal do you have a social media or a name that I could share?

I'd like you publicly thank you for your work. 😊

@adsonpleal
Copy link
Author

I'm one of those weird ones that don't use social media haha. I have a linkedin profile, though. I think you could use it hehe.

@adsonpleal
Copy link
Author

@rrousselGit I still didn't have time to update to the new riverpod 2.0 on devtools. It shouldn't be that hard, but I'm full with my work here and couldn't focus on that. Do you know how likely is to get the devtools PR approved? I'm wondering about the changes of that PR getting merged. Do you think is worth investing time making it ready for review?

I know that they are having discussions about creating some kind of api to create extensions to devtools. But is this actually happening on a near future?

Another alternative would be to have the riverpod inspector as a stand alone program, just like isar does with their isar inspector. With this we would have full control of the inspector, without the need of waiting for devtools approval. I know that having the tool inside devtools is way better, as de developer experience would be fully integrated with the flutter tooling. But maybe is worth going with this route, since we could have way faster releases on the riverpod devtools.

WDYT?

@adsonpleal
Copy link
Author

Hey @rrousselGit since the devtools PR was closed and this code is already too outdated I'll be closing this PR.

What do you think about the custom dev-tool idea?

@adsonpleal adsonpleal closed this Mar 2, 2023
@rrousselGit
Copy link
Owner

@adsonpleal Sorry I missed your reply.

Yeah I'm considering a custom devtool too. Maybe an extension in VScode

@adsonpleal
Copy link
Author

@adsonpleal Sorry I missed your reply.

Yeah I'm considering a custom devtool too. Maybe an extension in VScode

@rrousselGit Let me know if you need some help. I did some explorations here with the Isar idea (stand alone solution), but a VScode extension seems better. Or maybe even both, we could probably render the flutter web version inside a Webview in VSCode and have a stand alone web version as well.

We can explore https://github.com/Dart-Code/Dart-Code the official dart extension, they probably render the flutter stuff in VScode.

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.

2 participants