Skip to content

Commit 1a58637

Browse files
author
sylar
committed
fix: stabilize Flutter Windows build and auth startup
1 parent 7df8df2 commit 1a58637

5 files changed

Lines changed: 120 additions & 42 deletions

File tree

examples/flutter/RunAnywhereAI/lib/features/models/add_model_from_url_view.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ class _AddModelFromURLViewState extends State<AddModelFromURLView> {
182182
),
183183
const SizedBox(height: AppSpacing.mediumLarge),
184184
DropdownButtonFormField<LLMFramework>(
185-
initialValue: _selectedFramework,
185+
value: _selectedFramework,
186186
decoration: const InputDecoration(
187187
labelText: 'Target Framework',
188188
border: OutlineInputBorder(),

examples/flutter/RunAnywhereAI/lib/features/structured_output/structured_output_view.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ class _StructuredOutputViewState extends State<StructuredOutputView> {
367367
return Container(
368368
padding: const EdgeInsets.all(AppSpacing.large),
369369
child: DropdownButtonFormField<int>(
370-
initialValue: _selectedExampleIndex,
370+
value: _selectedExampleIndex,
371371
decoration: InputDecoration(
372372
labelText: 'Example',
373373
border: OutlineInputBorder(
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_test/flutter_test.dart';
3+
import 'package:runanywhere_ai/features/models/add_model_from_url_view.dart';
4+
import 'package:runanywhere_ai/features/structured_output/structured_output_view.dart';
5+
6+
void main() {
7+
testWidgets('StructuredOutputView builds with example selector',
8+
(tester) async {
9+
await tester.pumpWidget(
10+
const MaterialApp(
11+
home: Scaffold(
12+
body: StructuredOutputView(),
13+
),
14+
),
15+
);
16+
17+
expect(find.byType(StructuredOutputView), findsOneWidget);
18+
expect(find.byType(DropdownButtonFormField<int>), findsOneWidget);
19+
expect(find.text('Recipe'), findsOneWidget);
20+
});
21+
22+
testWidgets('AddModelFromURLView builds with framework selector',
23+
(tester) async {
24+
await tester.pumpWidget(
25+
MaterialApp(
26+
home: Scaffold(
27+
body: AddModelFromURLView(
28+
onModelAdded: (_) {},
29+
),
30+
),
31+
),
32+
);
33+
34+
expect(find.byType(AddModelFromURLView), findsOneWidget);
35+
expect(find.text('Framework'), findsOneWidget);
36+
expect(find.text('Target Framework'), findsOneWidget);
37+
});
38+
}

sdk/runanywhere-flutter/packages/runanywhere/lib/native/dart_bridge_auth.dart

Lines changed: 65 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import 'dart:async';
44
import 'dart:convert';
55
import 'dart:ffi';
6-
import 'dart:io' show Platform;
76

87
import 'package:ffi/ffi.dart';
98
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
@@ -105,8 +104,8 @@ class DartBridgeAuth {
105104
static void reset() {
106105
try {
107106
final lib = PlatformLoader.loadCommons();
108-
final resetFn =
109-
lib.lookupFunction<Void Function(), void Function()>('rac_auth_reset');
107+
final resetFn = lib
108+
.lookupFunction<Void Function(), void Function()>('rac_auth_reset');
110109
resetFn();
111110
} catch (e) {
112111
_logger.debug('rac_auth_reset not available: $e');
@@ -204,7 +203,8 @@ class DartBridgeAuth {
204203
try {
205204
const storage = FlutterSecureStorage(
206205
aOptions: AndroidOptions(encryptedSharedPreferences: true),
207-
iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock),
206+
iOptions:
207+
IOSOptions(accessibility: KeychainAccessibility.first_unlock),
208208
);
209209
deviceId = await storage.read(key: 'com.runanywhere.sdk.deviceId');
210210
if (deviceId != null && deviceId.isNotEmpty) {
@@ -244,7 +244,8 @@ class DartBridgeAuth {
244244
'Accept': 'application/json',
245245
};
246246

247-
final response = await http.post(url, headers: headers, body: requestJson);
247+
final response =
248+
await http.post(url, headers: headers, body: requestJson);
248249

249250
if (response.statusCode == 200 || response.statusCode == 201) {
250251
final authData = _parseAuthResponse(response.body);
@@ -285,7 +286,8 @@ class DartBridgeAuth {
285286
_logger.debug('Token needs refresh');
286287
final result = await refreshToken();
287288
if (!result.isSuccess) {
288-
_logger.warning('Token refresh failed', metadata: {'error': result.error});
289+
_logger
290+
.warning('Token refresh failed', metadata: {'error': result.error});
289291
// Return cached token anyway, server will reject if invalid
290292
return cachedToken ?? getAccessToken();
291293
}
@@ -300,8 +302,8 @@ class DartBridgeAuth {
300302
Future<void> clearAuth() async {
301303
try {
302304
final lib = PlatformLoader.loadCommons();
303-
final clearFn =
304-
lib.lookupFunction<Void Function(), void Function()>('rac_auth_clear');
305+
final clearFn = lib
306+
.lookupFunction<Void Function(), void Function()>('rac_auth_clear');
305307
clearFn();
306308

307309
// Also clear via state bridge
@@ -331,8 +333,9 @@ class DartBridgeAuth {
331333
bool needsRefresh() {
332334
try {
333335
final lib = PlatformLoader.loadCommons();
334-
final needsRefreshFn = lib.lookupFunction<Int32 Function(), int Function()>(
335-
'rac_auth_needs_refresh');
336+
final needsRefreshFn =
337+
lib.lookupFunction<Int32 Function(), int Function()>(
338+
'rac_auth_needs_refresh');
336339
return needsRefreshFn() != 0;
337340
} catch (e) {
338341
return false;
@@ -412,19 +415,23 @@ class DartBridgeAuth {
412415
try {
413416
final lib = PlatformLoader.loadCommons();
414417
final buildRequest = lib.lookupFunction<
415-
Pointer<Utf8> Function(Pointer<RacSdkConfigStruct>),
416-
Pointer<Utf8> Function(
417-
Pointer<RacSdkConfigStruct>)>('rac_auth_build_authenticate_request');
418+
Pointer<Utf8> Function(Pointer<RacSdkConfigStruct>),
419+
Pointer<Utf8> Function(Pointer<RacSdkConfigStruct>)>(
420+
'rac_auth_build_authenticate_request');
418421

419422
final config = calloc<RacSdkConfigStruct>();
420423
final apiKeyPtr = apiKey.toNativeUtf8();
421424
final deviceIdPtr = deviceId.toNativeUtf8();
422-
final buildTokenPtr = buildToken?.toNativeUtf8() ?? nullptr;
425+
final platformPtr = SDKConstants.platform.toNativeUtf8();
426+
final sdkVersionPtr = SDKConstants.version.toNativeUtf8();
423427

424428
try {
429+
config.ref.environment = 0;
425430
config.ref.apiKey = apiKeyPtr;
431+
config.ref.baseURL = nullptr;
426432
config.ref.deviceId = deviceIdPtr;
427-
config.ref.buildToken = buildTokenPtr.cast<Utf8>();
433+
config.ref.platform = platformPtr;
434+
config.ref.sdkVersion = sdkVersionPtr;
428435

429436
final result = buildRequest(config);
430437
if (result == nullptr) return null;
@@ -440,18 +447,18 @@ class DartBridgeAuth {
440447
} finally {
441448
calloc.free(apiKeyPtr);
442449
calloc.free(deviceIdPtr);
443-
if (buildTokenPtr != nullptr) calloc.free(buildTokenPtr);
450+
calloc.free(platformPtr);
451+
calloc.free(sdkVersionPtr);
444452
calloc.free(config);
445453
}
446454
} catch (e) {
447455
_logger.debug('rac_auth_build_authenticate_request error: $e');
448456
// Fallback: build JSON manually (must match C++ rac_auth_request_to_json format)
449457
// Backend expects snake_case keys: api_key, device_id, platform, sdk_version
450-
final platform = Platform.isAndroid ? 'android' : 'ios';
451458
final json = {
452459
'api_key': apiKey,
453460
'device_id': deviceId,
454-
'platform': platform,
461+
'platform': SDKConstants.platform,
455462
'sdk_version': SDKConstants.version,
456463
};
457464
_logger.debug('Auth request JSON: $json');
@@ -564,17 +571,17 @@ class DartBridgeAuth {
564571
data['access_token'] as String? ?? data['accessToken'] as String?;
565572
final refreshToken =
566573
data['refresh_token'] as String? ?? data['refreshToken'] as String?;
567-
final deviceId = data['device_id'] as String? ?? data['deviceId'] as String?;
574+
final deviceId =
575+
data['device_id'] as String? ?? data['deviceId'] as String?;
568576
final userId = data['user_id'] as String? ?? data['userId'] as String?;
569-
final organizationId =
570-
data['organization_id'] as String? ?? data['organizationId'] as String?;
577+
final organizationId = data['organization_id'] as String? ??
578+
data['organizationId'] as String?;
571579

572580
// Parse expiry - API returns expires_in (seconds until expiry)
573581
int? expiresAt;
574582
final expiresIn = data['expires_in'] as int?;
575583
if (expiresIn != null) {
576-
expiresAt =
577-
DateTime.now().millisecondsSinceEpoch ~/ 1000 + expiresIn;
584+
expiresAt = DateTime.now().millisecondsSinceEpoch ~/ 1000 + expiresIn;
578585
} else {
579586
expiresAt = data['expires_at'] as int? ?? data['expiresAt'] as int?;
580587
}
@@ -623,17 +630,22 @@ class DartBridgeAuth {
623630

624631
if (authData.accessToken != null && authData.accessToken!.isNotEmpty) {
625632
await storage.write(
626-
key: 'com.runanywhere.sdk.accessToken', value: authData.accessToken);
633+
key: 'com.runanywhere.sdk.accessToken',
634+
value: authData.accessToken);
627635
_secureCache['com.runanywhere.sdk.accessToken'] = authData.accessToken!;
628636
storedCount++;
629-
_logger.debug('Stored access token (${authData.accessToken!.length} chars)');
637+
_logger.debug(
638+
'Stored access token (${authData.accessToken!.length} chars)');
630639
}
631640
if (authData.refreshToken != null && authData.refreshToken!.isNotEmpty) {
632641
await storage.write(
633-
key: 'com.runanywhere.sdk.refreshToken', value: authData.refreshToken);
634-
_secureCache['com.runanywhere.sdk.refreshToken'] = authData.refreshToken!;
642+
key: 'com.runanywhere.sdk.refreshToken',
643+
value: authData.refreshToken);
644+
_secureCache['com.runanywhere.sdk.refreshToken'] =
645+
authData.refreshToken!;
635646
storedCount++;
636-
_logger.debug('Stored refresh token (${authData.refreshToken!.length} chars)');
647+
_logger.debug(
648+
'Stored refresh token (${authData.refreshToken!.length} chars)');
637649
}
638650
if (authData.deviceId != null && authData.deviceId!.isNotEmpty) {
639651
await storage.write(
@@ -643,19 +655,23 @@ class DartBridgeAuth {
643655
_logger.debug('Stored device ID: ${authData.deviceId}');
644656
}
645657
if (authData.userId != null && authData.userId!.isNotEmpty) {
646-
await storage.write(key: 'com.runanywhere.sdk.userId', value: authData.userId);
658+
await storage.write(
659+
key: 'com.runanywhere.sdk.userId', value: authData.userId);
647660
_secureCache['com.runanywhere.sdk.userId'] = authData.userId!;
648661
storedCount++;
649662
}
650-
if (authData.organizationId != null && authData.organizationId!.isNotEmpty) {
663+
if (authData.organizationId != null &&
664+
authData.organizationId!.isNotEmpty) {
651665
await storage.write(
652-
key: 'com.runanywhere.sdk.organizationId', value: authData.organizationId);
666+
key: 'com.runanywhere.sdk.organizationId',
667+
value: authData.organizationId);
653668
_secureCache['com.runanywhere.sdk.organizationId'] =
654669
authData.organizationId!;
655670
storedCount++;
656671
}
657672

658-
_logger.debug('Auth tokens stored in secure storage ($storedCount items)');
673+
_logger
674+
.debug('Auth tokens stored in secure storage ($storedCount items)');
659675
} catch (e) {
660676
_logger.debug('Failed to store auth tokens: $e');
661677
}
@@ -680,11 +696,14 @@ class DartBridgeAuth {
680696
iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock),
681697
);
682698

683-
final accessToken = await storage.read(key: 'com.runanywhere.sdk.accessToken');
684-
final refreshToken = await storage.read(key: 'com.runanywhere.sdk.refreshToken');
699+
final accessToken =
700+
await storage.read(key: 'com.runanywhere.sdk.accessToken');
701+
final refreshToken =
702+
await storage.read(key: 'com.runanywhere.sdk.refreshToken');
685703
final deviceId = await storage.read(key: 'com.runanywhere.sdk.deviceId');
686704
final userId = await storage.read(key: 'com.runanywhere.sdk.userId');
687-
final organizationId = await storage.read(key: 'com.runanywhere.sdk.organizationId');
705+
final organizationId =
706+
await storage.read(key: 'com.runanywhere.sdk.organizationId');
688707

689708
if (accessToken != null) {
690709
_secureCache['com.runanywhere.sdk.accessToken'] = accessToken;
@@ -761,8 +780,8 @@ int _secureStoreCallback(
761780
}
762781

763782
/// Retrieve callback
764-
int _secureRetrieveCallback(
765-
Pointer<Utf8> key, Pointer<Utf8> outValue, int bufferSize, Pointer<Void> context) {
783+
int _secureRetrieveCallback(Pointer<Utf8> key, Pointer<Utf8> outValue,
784+
int bufferSize, Pointer<Void> context) {
766785
if (key == nullptr || outValue == nullptr) return -1;
767786

768787
try {
@@ -845,8 +864,8 @@ typedef RacSecureStoreCallbackNative = Int32 Function(
845864
Pointer<Utf8> key, Pointer<Utf8> value, Pointer<Void> context);
846865

847866
/// Secure storage retrieve callback
848-
typedef RacSecureRetrieveCallbackNative = Int32 Function(
849-
Pointer<Utf8> key, Pointer<Utf8> outValue, IntPtr bufferSize, Pointer<Void> context);
867+
typedef RacSecureRetrieveCallbackNative = Int32 Function(Pointer<Utf8> key,
868+
Pointer<Utf8> outValue, IntPtr bufferSize, Pointer<Void> context);
850869

851870
/// Secure storage delete callback
852871
typedef RacSecureDeleteCallbackNative = Int32 Function(
@@ -861,10 +880,16 @@ base class RacSecureStorageCallbacksStruct extends Struct {
861880
}
862881

863882
/// SDK config struct for auth requests
883+
/// Must exactly match C++ `rac_sdk_config_t`.
864884
base class RacSdkConfigStruct extends Struct {
885+
@Int32()
886+
external int environment;
887+
865888
external Pointer<Utf8> apiKey;
889+
external Pointer<Utf8> baseURL;
866890
external Pointer<Utf8> deviceId;
867-
external Pointer<Utf8> buildToken;
891+
external Pointer<Utf8> platform;
892+
external Pointer<Utf8> sdkVersion;
868893
}
869894

870895
// =============================================================================
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import 'dart:ffi';
2+
3+
import 'package:flutter_test/flutter_test.dart';
4+
import 'package:runanywhere/native/dart_bridge_auth.dart' as auth_bridge;
5+
import 'package:runanywhere/native/dart_bridge_environment.dart'
6+
as environment_bridge;
7+
8+
void main() {
9+
test('auth SDK config struct matches native SDK config layout', () {
10+
expect(
11+
sizeOf<auth_bridge.RacSdkConfigStruct>(),
12+
sizeOf<environment_bridge.RacSdkConfigStruct>(),
13+
);
14+
});
15+
}

0 commit comments

Comments
 (0)