Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 18 additions & 37 deletions examples/flutter-demo-app/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Turnkey Flutter Demo App

This demo app leverages Turnkey's Dart/Flutter packages to demonstrate how they can be used to create a fully functional application. It includes a simple Node.js backend API server to facilitate server-side operations.
This demo app leverages [`turnkey_sdk_flutter`](../../packages/sdk-flutter/) to demonstrate how they can be used to create a fully functional application.

## Demo

https://github.com/user-attachments/assets/3d583ed8-1eff-4101-ae43-3c76c655e635
![Demo](../../assets/demo.gif)

## Prerequisites

Expand All @@ -16,49 +16,34 @@ https://github.com/user-attachments/assets/3d583ed8-1eff-4101-ae43-3c76c655e635
| Dart | >= 3.0.0 |
| Xcode | >= 12.0 |
| Android Studio | >= 4.0 |
| Node.js | >= 14.0 |

## Environment Variables Setup

Create a `.env` file in the root directory of your project. You can use the provided `.env.example` file as a template:

```python
# Public Turnkey API
TURNKEY_API_URL="https://api.turnkey.com"
BACKEND_API_URL="http://localhost:3000"
ORGANIZATION_ID="<YOUR_ORGANIZATION_ID>"
ORGANIZATION_ID="YOUR_ORGANIZATION_ID_HERE"

# PASSKEY ENV VARIABLES
RP_ID="<YOUR_RP_ID>" # This is the relying party ID that hosts your .well-known file
# Auth Proxy
AUTH_PROXY_URL="https://authproxy.turnkey.com"
AUTH_PROXY_CONFIG_ID="YOUR_AUTH_PROXY_CONFIG_ID"

# GOOGLE AUTH ENV VARIABLES
GOOGLE_CLIENT_ID="<YOUR_GOOGLE_WEB_CLIENT_ID>"
APP_SCHEME="flutter-demo-app" # This is the scheme used for OAuth redirects in the app. It should match the one used in the iOS and Android projects.

#NODE SERVER ENV VARIABLES (Only used for the Node server in /api-server)
TURNKEY_API_PUBLIC_KEY="<YOUR_TURNKEY_API_PUBLIC_KEY>"
TURNKEY_API_PRIVATE_KEY="<YOUR_TURNKEY_API_PRIVATE_KEY>"
BACKEND_API_PORT="3000"
```
# Passkey
RP_ID="YOUR_RP_ID_HERE"

## Backend API Server
# OAuth
APP_SCHEME="your-app-scheme"

This app must be connected to a backend server. You can use the included Node.js backend API server or set up your own.
GOOGLE_CLIENT_ID="YOUR_GOOGLE_CLIENT_ID"
APPLE_CLIENT_ID="YOUR_APPLE_CLIENT_ID"
X_CLIENT_ID="YOUR_X_CLIENT_ID"
DISCORD_CLIENT_ID="YOUR_DISCORD_CLIENT_ID"

### Install Dependencies

Navigate to the api-server directory and install the dependencies:

```bash
cd api-server
npm install
```

Build and Run the Backend Server

```bash
npm run build
npm start
```
You can find your `ORGANIZATION_ID` and `AUTH_PROXY_CONFIG_ID` from the [Turnkey Dashboard](https://app.turnkey.com).

## Running the Flutter App

Expand All @@ -82,7 +67,7 @@ You will be prompted to select a device to run the app on. You can also use the

## OAuth Configuration (optional)

This app includes an example for authenticating with Turnkey using a Google or Apple account.
This app includes an example for authenticating with Turnkey using a Google, Apple, X, or Discord account.

### Sign in with Google

Expand Down Expand Up @@ -110,11 +95,7 @@ In your project's `.env` file, add the following:
GOOGLE_CLIENT_ID="<YOUR_GOOGLE_WEB_CLIENT_ID>"
```

### Sign in with Apple

Signing in with Apple leverages the [sign_in_with_apple](https://pub.dev/packages/sign_in_with_apple) packages. This allows Apple's native [Sign in With Apple SDK](https://developer.apple.com/documentation/signinwithapple) to be used in Flutter.

To enable this feature, simply [add the **Sign in with Apple** capability to your app in Xcode.](https://developer.apple.com/documentation/xcode/adding-capabilities-to-your-app)
For more information on how to setup OAuth in Turnkey powered Flutter apps, visit [our docs!](https://docs.turnkey.com/sdks/flutter)

## Passkey Configuration (optional)

Expand Down
37 changes: 12 additions & 25 deletions examples/flutter-demo-app/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
PODS:
- app_links (6.4.1):
- Flutter
- device_info_plus (0.0.1):
- Flutter
- Flutter (1.0.0)
- flutter_inappwebview_ios (0.0.1):
- Flutter
Expand All @@ -17,11 +15,8 @@ PODS:
- flutter_secure_storage (6.0.0):
- Flutter
- OrderedSet (6.0.3)
- package_info_plus (0.4.5):
- Flutter
- passkeys_darwin (0.0.1):
- passkeys_ios (0.0.1):
- Flutter
- FlutterMacOS
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
Expand All @@ -33,13 +28,11 @@ PODS:

DEPENDENCIES:
- app_links (from `.symlinks/plugins/app_links/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- Flutter (from `Flutter`)
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
- flutter_libphonenumber_ios (from `.symlinks/plugins/flutter_libphonenumber_ios/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- passkeys_darwin (from `.symlinks/plugins/passkeys_darwin/darwin`)
- passkeys_ios (from `.symlinks/plugins/passkeys_ios/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`)
- ua_client_hints (from `.symlinks/plugins/ua_client_hints/ios`)
Expand All @@ -52,8 +45,6 @@ SPEC REPOS:
EXTERNAL SOURCES:
app_links:
:path: ".symlinks/plugins/app_links/ios"
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
Flutter:
:path: Flutter
flutter_inappwebview_ios:
Expand All @@ -62,10 +53,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_libphonenumber_ios/ios"
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
passkeys_darwin:
:path: ".symlinks/plugins/passkeys_darwin/darwin"
passkeys_ios:
:path: ".symlinks/plugins/passkeys_ios/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
sign_in_with_apple:
Expand All @@ -74,19 +63,17 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/ua_client_hints/ios"

SPEC CHECKSUMS:
app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
app_links: 585674be3c6661708e6cd794ab4f39fb9d8356f9
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
flutter_libphonenumber_ios: 6bd2fac9dfb8f37c5732451fb7c9a992f1088d86
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
flutter_libphonenumber_ios: 5ad8620197bff91dd159d828b7544ec6956e86a1
flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
passkeys_darwin: 0a09d3b26cea8de1ec3bb8f8c7c159981f63bbc7
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
passkeys_ios: fdae8c06e2178a9fcb9261a6cb21fb9a06a81d53
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
PhoneNumberKit: ec00ab8cef5342c1dc49fadb99d23fa7e66cf0ef
sign_in_with_apple: c5dcc141574c8c54d5ac99dd2163c0c72ad22418
ua_client_hints: 92fe0d139619b73ec9fcb46cc7e079a26178f586
sign_in_with_apple: f3bf75217ea4c2c8b91823f225d70230119b8440
ua_client_hints: aeabd123262c087f0ce151ef96fa3ab77bfc8b38

PODFILE CHECKSUM: 4f1c12611da7338d21589c0b2ecd6bd20b109694

Expand Down
103 changes: 48 additions & 55 deletions examples/flutter-demo-app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,23 @@ void main() async {
WidgetsFlutterBinding.ensureInitialized();
await loadEnv();

void onSessionSelected(Session session) {
if (isValidSession(session)) {
WidgetsBinding.instance.addPostFrameCallback((_) {
navigatorKey.currentState?.pushReplacement(
MaterialPageRoute(builder: (context) => const DashboardScreen()),
);
final ctx = navigatorKey.currentContext;
if (ctx != null) {
ScaffoldMessenger.of(ctx).showSnackBar(
const SnackBar(
content: Text('Logged in! Redirecting to the dashboard.'),
),
void onSessionSelected(Session session) {
if (isValidSession(session)) {
WidgetsBinding.instance.addPostFrameCallback((_) {
navigatorKey.currentState?.pushReplacement(
MaterialPageRoute(builder: (context) => const DashboardScreen()),
);
}
});
final ctx = navigatorKey.currentContext;
if (ctx != null) {
ScaffoldMessenger.of(ctx).showSnackBar(
const SnackBar(
content: Text('Logged in! Redirecting to the dashboard.'),
),
);
}
});
}
}
}


void onSessionCleared(Session session) {
navigatorKey.currentState?.pushReplacementNamed('/');
Expand All @@ -43,23 +42,9 @@ void onSessionSelected(Session session) {
}
}

void onInitialized(Object? error) {
final ctx = navigatorKey.currentContext;
if (error != null) {
debugPrint('Turnkey initialization failed: $error');
if (ctx != null) {
ScaffoldMessenger.of(ctx).showSnackBar(
SnackBar(content: Text('Failed to initialize Turnkey: $error')),
);
}
} else {
debugPrint('Turnkey initialized successfully');
}
}

final createSubOrgParams = CreateSubOrgParams(
customWallet: CustomWallet(
walletName: "Wallet 1",
walletName: 'Wallet 1',
walletAccounts: [
v1WalletAccountParams(
addressFormat: v1AddressFormat.address_format_ethereum,
Expand Down Expand Up @@ -100,13 +85,10 @@ void onSessionSelected(Session session) {
),
onSessionSelected: onSessionSelected,
onSessionCleared: onSessionCleared,
onInitialized: onInitialized,
),
);

turnkeyProvider.ready.then((_) {
debugPrint('Turnkey is ready');
}).catchError((error) {
turnkeyProvider.ready.catchError((error) {
debugPrint('Caught from .ready: $error');

// Schedule the snackbar to show after the current frame
Expand Down Expand Up @@ -134,29 +116,40 @@ class MyApp extends StatelessWidget {

@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color.fromARGB(255, 0, 26, 255)),
useMaterial3: true,
),
home: const HomePage(title: 'Turnkey Flutter Demo App'),
return Consumer<TurnkeyProvider>(
builder: (context, turnkey, _) {
return MaterialApp(
navigatorKey: navigatorKey,
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color.fromARGB(255, 0, 26, 255),
),
useMaterial3: true,
),
home: _buildHome(turnkey),
);
},
);
}
}

class HomePage extends StatelessWidget {
const HomePage({super.key, required this.title});

final String title;
Widget _buildHome(TurnkeyProvider turnkey) {
switch (turnkey.authState) {
case AuthState.loading:
// Provider is booting: show splash / spinner.
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: const LoginScreen(),
);
default:
// Turnkey is ready. Show the login screen.
return Scaffold(
appBar: AppBar(
title: Text('Turnkey Flutter Demo App'),
),
body: LoginScreen(),
);
// We'll have the `onSessionSelected` callback navigate to the dashboard screen. You can also add another case here for AuthState.authenticated if you want to handle it directly.
}
}
}
Loading