-
Notifications
You must be signed in to change notification settings - Fork 325
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
Offline support for Network page #8332
base: master
Are you sure you want to change the base?
Offline support for Network page #8332
Conversation
@hrajwade96 is this PR ready for review or is this still a work in progress? |
@kenzieschmoll just finishing up few things, I will mark it ready soon |
…into network_screen_offline_support
unawaited(controller.startRecording()); | ||
|
||
cancelListeners(); | ||
try { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of doing this logic in the screen, we want to do this in the screen's controller NetworkController
. Checkout the guide in the dart doc for OfflineScreenControllerMixin. This provides pretty thorough documentation on how offline support for a screen should be set up: https:/flutter/devtools/blob/master/packages/devtools_app/lib/src/shared/offline_data.dart/#L66-L124.
You can also use the ProfilerScreenController as an example: https:/flutter/devtools/blob/master/packages/devtools_app/lib/src/screens/profiler/profiler_screen_controller.dart/#L81
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure how I missed it, apologies. I have fixed it now and scanning through if I have missed anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you confirm if the changes are correct?
filteredData.value.whereType<DartIOHttpRequestData>().toList(); | ||
return OfflineScreenData( | ||
screenId: NetworkScreen.id, | ||
data: serializeRequestDataForOfflineMode(requests), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recommend creating a class OfflineNetworkData with Serializable
that packages up all of the data stored on the Network controller for serialization. This includes the list of requests as well as other information like the currently selected request. In the future we may want to include the current filter, but let's leave that out for now.
Creating a new class OfflineNetworkData
will make it easier to test the toJson
and fromJson
methods that should be implemented on the class. See other examples of classes that mixin Serializable
in DevTools for an example.
Then here, you'd create an instance of OfflineNetworkData
from the current data on the network controller, and call toJson()
to get the value for the data
parameter of OfflineScreenData
in this return statement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, sure!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
@@ -160,6 +167,58 @@ class NetworkController extends DisposableController | |||
@visibleForTesting | |||
bool get isPolling => _pollingTimer != null; | |||
|
|||
void _initHelper() async { | |||
try { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need the try / catch here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done, removed it.
packages/devtools_app/lib/src/screens/network/network_controller.dart
Outdated
Show resolved
Hide resolved
packages/devtools_app/lib/src/screens/network/network_controller.dart
Outdated
Show resolved
Hide resolved
shouldLoad: (data) => !data.isEmpty, | ||
loadData: (data) => loadOfflineData(data), | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the code after this if statement should be put in an else
block
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not using else was an oversight, added else block now, and also now there is no code outside.
loadData: (data) => loadOfflineData(data), | ||
); | ||
} | ||
cancelListeners(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can remove this line since this initialization code should only be run once. When this was in the widget code, we needed to call cancelListeners
because didChangeDependencies
may be called more than once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok got it, removed
packages/devtools_app/lib/src/screens/network/offline_network_data.dart
Outdated
Show resolved
Hide resolved
@@ -187,7 +180,7 @@ class _NetworkProfilerControlsState extends State<_NetworkProfilerControls> | |||
@override | |||
void initState() { | |||
super.initState(); | |||
|
|||
addAutoDisposeListener(offlineDataController.showingOfflineData); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can remove this if you use OfflineAwareControls instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok done
final List<dynamic> requestsJson = json['requests'] ?? []; | ||
final requests = requestsJson |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need to track the socket statistics as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes sure, will add it, I knew it is required but thought of first doing it for http requests to start with, and later add the implementation for socket reqs.
'requests': requests.map((request) => request.toJson()).toList(), | ||
'selectedRequestId': selectedRequestId, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
store these strings as static consts on this class or as an enum in this file
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok
timelineMicrosOffset: DateTime.now().microsecondsSinceEpoch - | ||
(networkService.timeStamp ?? 0), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can just use DateTime.now().microsecondsSinceEpoch
for this. This is arbitrary for offline data since we won't be updating the sockets like we do when we have a live connection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
OfflineAwareControls( | ||
controlsBuilder: (_) => _NetworkProfilerControls( | ||
controller: controller, | ||
offline: offlineDataController.showingOfflineData.value, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use the value from the controlsBuilder
instead of looking this up from the notifier again:
controlsBuilder: (offline) => _NetworkProfilerControls(
controller: controller,
offline: offline,
),
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
SearchField<NetworkController>( | ||
searchController: widget.controller, | ||
searchFieldEnabled: hasRequests, | ||
searchFieldWidth: screenWidth <= MediaSize.xs | ||
? defaultSearchFieldWidth | ||
: wideSearchFieldWidth, | ||
), | ||
const SizedBox(width: denseSpacing), | ||
DevToolsFilterButton( | ||
onPressed: _showFilterDialog, | ||
isFilterActive: _filteredRequests.length != _requests.length, | ||
), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only include these two controls in offline mode.
children: [
if(!widget.offline) ...[
StartStopRecordingButton(...)
const SizedBox(width: denseSpacing),
ClearButton(...)
const SizedBox(width: denseSpacing),
DownloadButton(...)
const SizedBox(width: denseSpacing),
]
Spacer(), // this is a helper widget that replaces Expanded(child: SizedBox()),
const SizedBox(width: denseSpacing),
SearchField<NetworkController>(...)
const SizedBox(width: denseSpacing),
DevToolsFilterButton(...),
],
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok reverting this and adding Spacer.
Actually I had changed it again thinking you meant keeping all the controls, which I got clarified later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
currentRequests: _networkService.currentHttpRequests, | ||
socketStats: _networkService.sockets ?? [], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't have to make any changes to the _networkService to do this. I think what you want is this:
mixin Serializable
to the NetworkRequest
class. This will force subclasses of NetworkRequest
to implement a toJson
method.
Then implement toJson
for the NetworkRequest
subclasses DartIOHttpRequestData
and Socket
. You will also need to implement the deserialization logic fromJson
on these classes as well if they are not already implemented.
Then here you can create offline data as follows:
final httpRequestData = <DartIOHttpRequestData>[];
final socketData = <Socket>[];
for (final request in _currentNetworkRequests.value) {
if (request is DartIOHttpRequestData) {
httpRequestData.add(request);
} else if (request is Socket) {
socketData.add(request);
}
}
final offlineData = OfflineNetworkData(
httpRequestData: httpRequestData,
socketData: socketData,
selectedRequestId: selectedRequest.value?.id,
);
In OfflineNetworkData
toJson
and fromJson
methods, you can use the serialization methods you implemented on the Socket
and DartIORequestData
classes.
Let me know if you have any questions on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will give the reqs lists of - DartIOHttpRequestData and Socket classes.
I had implemented this for DartIOHttpRequestData, similarly I can do for socket, by using Serializable mixin on their parent abstract class.
However, you suggested to use the updateOrAddAll
function
void updateOrAddAll({
required List<HttpProfileRequest> requests,
required List<SocketStatistic> sockets,
required int timelineMicrosOffset,
})
Which requires below lists :
List<HttpProfileRequest>
& List<SocketStatistic>.
And I think this data is only available in NetworkService, also there are no getters for this data in DartIOHttpRequestData and Socket classes.
Or I should create those getters and retrieve it from there, is that what you were suggesting?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Socket
class wraps a SocketStatistic
object, so you can get the List<SocketStatistic>
from the List<Socket>
. I recommend adding this extension method to the bottom of the network_model.dart
file so that you don't have to change the visibility of Socket._socket
.
extension SocketExtension on List<Socket> {
List<SocketStatistic> get mapToSocketStatistics {
return map((socket) => socket._socket).toList();
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also SocketStatistic
already has a fromJson
method you can leverage. I expect the serialization logic for Socket
would be something simple like:
Map<String, Object?> toJson() {
return {
'timelineMicrosBase': _timelineMicrosBase,
'socket': _socket.toJson() // you'll need to implement this, perhaps as an extension method on SocketStatistic
};
}
Socket fromJson(Map<String, Object?> json) {
return Socket(
SocketStatistic.fromJson((json['socket'] as Map).cast<String, Object?>()),
json['timelineMicrosBase'] as int,
);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh ok, I think visibility can remain intact even with :
SocketStatistic get socketStatistic => _socket;
But yes extension adds more utility here, will use it.
Adding offline support to Network page.
issue link - #4470
List which issues are fixed by this PR.
Please add a note to
packages/devtools_app/release_notes/NEXT_RELEASE_NOTES.md
if your change requires release notes. Otherwise, add the 'release-notes-not-required' label to the PR.Pre-launch Checklist
///
).If you need help, consider asking for help on Discord.