Skip to content

Conversation

@handgull
Copy link

@handgull handgull commented Oct 5, 2025

Please do not merge this.

Hi, this code isn’t working, but I gave it a try. Could you take a look at it? Honestly, I’ve never used this approach to call Dart code from JavaScript event handlers.
I’d like to take this opportunity to ask which other callbacks you consider useful to add at this point, besides the click event.

@handgull handgull force-pushed the feat/chart-callbacks branch 6 times, most recently from 130f4fa to fa59e22 Compare October 26, 2025 09:28
@handgull handgull force-pushed the feat/chart-callbacks branch from fa59e22 to 6f4c6bb Compare October 26, 2025 09:54
@handgull
Copy link
Author

handgull commented Oct 26, 2025

@warioddly now should be fine, sorry for the confusion. My project works well :)

@warioddly
Copy link
Owner

warioddly commented Nov 7, 2025

hi @handgull , it's great to see you again

huge thanks for the contribution, I'll check the PR when I'm free

@warioddly warioddly added the enhancement New feature or request label Nov 7, 2025
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces a major refactoring of the Graphify Flutter charting library, adding chart click event handling, modernizing the architecture, and updating dependencies. The changes include significant API modifications that may impact existing users.

Key Changes:

  • Added chart click event support with chartClickedEvent stream on GraphifyController
  • Refactored view architecture to use inheritance instead of composition, with GraphifyView extending StatefulWidget
  • Updated to use dart.library.js_interop instead of deprecated dart.library.html for web platform detection

Reviewed Changes

Copilot reviewed 27 out of 29 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
pubspec.yaml Version bump to 1.2.1, removed Linux platform support
lib/src/view/_interface.dart Refactored GraphifyView to extend StatefulWidget, added @nonvirtual annotations
lib/src/view/implements/web.dart Updated web implementation with click event shim and refactored to use inheritance
lib/src/view/implements/mobile.dart Updated mobile implementation with JavaScript channel for click events
lib/src/view/implements/facade.dart Updated facade to match new architecture
lib/src/controller/interface.dart Added chartClickedEvent stream, removed Disposable mixin
lib/src/controller/implements/web.dart Implemented click event handling with window message listener
lib/src/controller/implements/mobile.dart Implemented click event handling with JavaScript channel
lib/src/utils/color_extension.dart Refactored color-to-RGBA conversion (contains bugs)
lib/src/utils/uid.dart New secure UID generator replacing Utils class
lib/src/resources/dependencies.js.dart Added initClickListener JavaScript function for chart interactions
example/lib/charts/chart_click.dart New example demonstrating chart click functionality
README.md Fixed typos in GitHub URLs
CHANGELOG.md Added 1.2.0 release notes
android/build.gradle Updated Kotlin to 2.1.0 and Gradle to 8.7.0

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +39 to +66
function safeValue(value, depth) {
if (value == null) return null;
if (depth > 3) return undefined;
const t = typeof value;
if (t === 'string' || t === 'number' || t === 'boolean') return value;
if (Array.isArray(value)) return value.map(v => safeValue(v, depth + 1)).filter(v => v !== undefined);
if (t === 'object') {
const out = {};
const keys = ['type', 'componentType', 'seriesType', 'seriesIndex', 'seriesName', 'name', 'dataIndex', 'data', 'value', 'color', 'marker'];
keys.forEach(k => {
if (k in value) {
const v = safeValue(value[k], depth + 1);
if (v !== undefined) out[k] = v;
}
});
// fallback: pick plain props if 'data' is missing
if (!('data' in out) && value && typeof value === 'object') {
Object.keys(value).forEach(k => {
if (out[k] !== undefined) return;
const v = value[k];
const vt = typeof v;
if (vt === 'string' || vt === 'number' || vt === 'boolean') out[k] = v;
});
}
return out;
}
return undefined;
}
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The safeValue function has a depth limit of 3 (line 41) but doesn't handle the case when depth exceeds 3 for non-primitive values. When depth > 3, it returns undefined, which is then filtered out in arrays (line 44). However, for objects, when a value at depth > 3 is encountered, undefined is stored in the object (line 51), which could lead to inconsistent data. Consider handling this more explicitly or documenting the depth limitation.

Copilot uses AI. Check for mistakes.
android:
ios:
linux:
macos:
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

Linux platform support has been removed from the platforms list. If this is intentional, ensure that this breaking change is clearly documented in the CHANGELOG and that users are notified. If Linux was previously supported and is being removed, this should be a major version bump (2.0.0) rather than a minor version bump (1.2.1).

Copilot uses AI. Check for mistakes.
..id = 'graphify-click-channel-shim-$uid'
..innerHTML = '''
(function(uid){
// Definisci il channel solo se non già presente
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The comment on line 78 is in Italian: "Definisci il channel solo se non già presente" (Define the channel only if not already present). Comments should be in English for consistency and accessibility to all contributors.

Suggested change
// Definisci il channel solo se non già presente
// Define the channel only if not already present

Copilot uses AI. Check for mistakes.
chartId: uid,
payload: String(payload)
});
// Inoltra come stringa: il controller web si aspetta un JSON string
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The comment on line 88 is in Italian: "Inoltra come stringa: il controller web si aspetta un JSON string" (Forward as string: the web controller expects a JSON string). Comments should be in English for consistency and accessibility to all contributors.

Suggested change
// Inoltra come stringa: il controller web si aspetta un JSON string
// Forward as string: the web controller expects a JSON string

Copilot uses AI. Check for mistakes.
Comment on lines +27 to 32
void dispose() async {
await _clicks.close();
try {
await _eval(JsMethods.disposeChart, [_quotedUid]);
} catch (_) {}
}
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The dispose() method is declared with void return type but contains an await statement on line 28. This creates a "fire-and-forget" async operation that could lead to incomplete cleanup. Change the return type to Future<void> and ensure callers properly await the disposal.

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +8
final r255 = (r * 255.0).round() & 0xff;
final g255 = (g * 255.0).round() & 0xff;
final b255 = (b * 255.0).round() & 0xff;
final alpha = a; // 0..1
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The new implementation references properties r, g, b, and a that don't exist on the Color class in Dart. The Color class uses red, green, blue, and opacity properties (as 0-255 integers for RGB and 0.0-1.0 for opacity). This code will fail at runtime. Use the correct property names: red, green, blue, and opacity.

Suggested change
final r255 = (r * 255.0).round() & 0xff;
final g255 = (g * 255.0).round() & 0xff;
final b255 = (b * 255.0).round() & 0xff;
final alpha = a; // 0..1
final r255 = red;
final g255 = green;
final b255 = blue;
final alpha = opacity; // 0..1

Copilot uses AI. Check for mistakes.
});
..srcdoc = indexHtml(id: uid).toJS
..onLoad.listen((_) => controller.update(widget.initialOptions))
..onError.listen(widget.onConsoleMessage);
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The onConsoleMessage callback signature has changed from void Function(String message) to void Function(Object message). In the web implementation, the error event is now passed directly without converting to string. This could break existing code that expects a String parameter. Consider maintaining backward compatibility or documenting this breaking change more clearly.

Suggested change
..onError.listen(widget.onConsoleMessage);
..onError.listen((event) => widget.onConsoleMessage?.call(event.toString()));

Copilot uses AI. Check for mistakes.
Comment on lines 65 to 67
webViewController
..clearLocalStorage()
..clearCache();
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The webViewController cleanup operations (clearLocalStorage() and clearCache()) are chained without awaiting. If these are async operations, this could lead to incomplete cleanup before the widget is fully disposed. Consider awaiting these operations if they return futures.

Copilot uses AI. Check for mistakes.
return GraphifyView(
controller: controller,
onConsoleMessage: (message) {
onConsoleMessage: <String>(message) {
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The type annotation <String> on line 38 is inconsistent with the new OnConsoleMessage signature which expects Object message. This will cause a type mismatch error. The parameter type should be Object or removed to allow type inference.

Suggested change
onConsoleMessage: <String>(message) {
onConsoleMessage: (message) {

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +11
Stream<Map<String, dynamic>> get chartClickedEvent =>
throw UnimplementedError("chartClickedEvent is not implemented");
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The chartClickedEvent getter throws an UnimplementedError instead of returning a Stream. This will cause runtime errors when accessed. Consider returning a never-emitting Stream (e.g., Stream.empty()) or a closed StreamController's stream to maintain consistent API behavior.

Suggested change
Stream<Map<String, dynamic>> get chartClickedEvent =>
throw UnimplementedError("chartClickedEvent is not implemented");
Stream<Map<String, dynamic>> get chartClickedEvent => Stream.empty();

Copilot uses AI. Check for mistakes.
@handgull handgull force-pushed the feat/chart-callbacks branch from 3766ac8 to cb62649 Compare November 12, 2025 08:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants