App crashing when I try to use app_auth with flutter_downloader in a project
NoshinDev opened this issue · 3 comments
I tried to add app_auth authorization to this basic project: https://github.com/fluttercommunity/flutter_downloader/blob/master/example/lib/main.dart
But after creating an authorization page and an authorization button redirecting to the web for data entry, the application crashes and loses connection to the project. If you skip the authorization and try to download the file there will be no problem, but if you erase all the implementations of flutter_downloader except the file pubscpec.yaml
the result will be the crash of the application
My pubspec.yaml
file looks as follows:
name: flutter_downloader_example
description: Demonstrates how to use the flutter_downloader plugin.
version: 1.0.0+1
publish_to: none
environment:
sdk: ">=2.17.0 <3.0.0"
flutter: ">=3.0.0 <4.0.0"
dependencies:
android_path_provider: ^0.3.0
device_info_plus: ^8.0.0
flutter:
sdk: flutter
flutter_appauth: ^4.2.0
flutter_downloader: ^1.9.1
http: ^0.13.5
path_provider: ^2.0.11
permission_handler: ^10.0.0
dev_dependencies:
flutter_test:
sdk: flutter
leancode_lint: ^2.0.0+1
flutter:
uses-material-design: true
Authorization file:
import 'dart:convert';
import 'dart:io' show Platform;
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_appauth/flutter_appauth.dart';
import 'package:http/http.dart' as http;
class Auth extends StatefulWidget {
const Auth({super.key});
@override
State<Auth> createState() => _AuthState();
}
class _AuthState extends State<Auth> {
bool _isBusy = false;
final FlutterAppAuth _appAuth = const FlutterAppAuth();
String? _codeVerifier;
String? _nonce;
String? _authorizationCode;
String? _refreshToken;
String? _accessToken;
String? _idToken;
final TextEditingController _authorizationCodeTextController =
TextEditingController();
final TextEditingController _accessTokenTextController =
TextEditingController();
final TextEditingController _accessTokenExpirationTextController =
TextEditingController();
final TextEditingController _idTokenTextController = TextEditingController();
final TextEditingController _refreshTokenTextController =
TextEditingController();
String? _userInfo;
// For a list of client IDs, go to https://demo.duendesoftware.com
final String _clientId = 'interactive.public';
final String _redirectUrl = 'com.duendesoftware.demo:/oauthredirect';
final String _issuer = 'https://demo.duendesoftware.com';
final String _discoveryUrl =
'https://demo.duendesoftware.com/.well-known/openid-configuration';
final String _postLogoutRedirectUrl = 'com.duendesoftware.demo:/';
final List<String> _scopes = <String>[
'openid',
'profile',
'email',
'offline_access',
'api'
];
final AuthorizationServiceConfiguration _serviceConfiguration =
const AuthorizationServiceConfiguration(
authorizationEndpoint: 'https://demo.duendesoftware.com/connect/authorize',
tokenEndpoint: 'https://demo.duendesoftware.com/connect/token',
endSessionEndpoint: 'https://demo.duendesoftware.com/connect/endsession',
);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Visibility(
visible: _isBusy,
child: const LinearProgressIndicator(),
),
const SizedBox(height: 8),
ElevatedButton(
child: const Text('Sign in with no code exchange'),
onPressed: () => _signInWithNoCodeExchange(),
),
ElevatedButton(
child: const Text('Refresh token'),
onPressed: _refreshToken != null ? _refresh : null,
),
const SizedBox(height: 8),
ElevatedButton(
child: const Text('End session'),
onPressed: _idToken != null
? () async {
await _endSession();
}
: null,
),
const SizedBox(height: 8),
const Text('authorization code'),
TextField(
controller: _authorizationCodeTextController,
),
const Text('access token'),
TextField(
controller: _accessTokenTextController,
),
const Text('access token expiration'),
TextField(
controller: _accessTokenExpirationTextController,
),
const Text('id token'),
TextField(
controller: _idTokenTextController,
),
const Text('refresh token'),
TextField(
controller: _refreshTokenTextController,
),
const Text('test api results'),
Text(_userInfo ?? ''),
],
),
),
),
),
);
}
Future<void> _endSession() async {
try {
_setBusyState();
await _appAuth.endSession(EndSessionRequest(
idTokenHint: _idToken,
postLogoutRedirectUrl: _postLogoutRedirectUrl,
serviceConfiguration: _serviceConfiguration));
_clearSessionInfo();
} catch (_) {}
_clearBusyState();
}
void _clearSessionInfo() {
setState(() {
_codeVerifier = null;
_nonce = null;
_authorizationCode = null;
_authorizationCodeTextController.clear();
_accessToken = null;
_accessTokenTextController.clear();
_idToken = null;
_idTokenTextController.clear();
_refreshToken = null;
_refreshTokenTextController.clear();
_accessTokenExpirationTextController.clear();
_userInfo = null;
});
}
Future<void> _refresh() async {
try {
_setBusyState();
final TokenResponse? result = await _appAuth.token(TokenRequest(
_clientId, _redirectUrl,
refreshToken: _refreshToken, issuer: _issuer, scopes: _scopes));
_processTokenResponse(result);
await _testApi(result);
} catch (_) {
_clearBusyState();
}
}
Future<void> _exchangeCode() async {
try {
_setBusyState();
final TokenResponse? result = await _appAuth.token(TokenRequest(
_clientId, _redirectUrl,
authorizationCode: _authorizationCode,
discoveryUrl: _discoveryUrl,
codeVerifier: _codeVerifier,
nonce: _nonce,
scopes: _scopes));
_processTokenResponse(result);
await _testApi(result);
} catch (_) {
_clearBusyState();
}
}
Future<void> _signInWithNoCodeExchange() async {
try {
_setBusyState();
// use the discovery endpoint to find the configuration
final AuthorizationResponse? result = await _appAuth.authorize(
AuthorizationRequest(_clientId, _redirectUrl,
discoveryUrl: _discoveryUrl, scopes: _scopes, loginHint: 'bob'),
);
// or just use the issuer
// var result = await _appAuth.authorize(
// AuthorizationRequest(
// _clientId,
// _redirectUrl,
// issuer: _issuer,
// scopes: _scopes,
// ),
// );
if (result != null) {
_processAuthResponse(result);
}
} catch (_) {
_clearBusyState();
}
}
Future<void> _signInWithNoCodeExchangeAndGeneratedNonce() async {
try {
_setBusyState();
final Random random = Random.secure();
final String nonce =
base64Url.encode(List<int>.generate(16, (_) => random.nextInt(256)));
// use the discovery endpoint to find the configuration
final AuthorizationResponse? result = await _appAuth.authorize(
AuthorizationRequest(_clientId, _redirectUrl,
discoveryUrl: _discoveryUrl,
scopes: _scopes,
loginHint: 'bob',
nonce: nonce),
);
if (result != null) {
_processAuthResponse(result);
}
} catch (_) {
_clearBusyState();
}
}
Future<void> _signInWithAutoCodeExchange(
{bool preferEphemeralSession = false}) async {
try {
_setBusyState();
// show that we can also explicitly specify the endpoints rather than getting from the details from the discovery document
final AuthorizationTokenResponse? result =
await _appAuth.authorizeAndExchangeCode(
AuthorizationTokenRequest(
_clientId,
_redirectUrl,
serviceConfiguration: _serviceConfiguration,
scopes: _scopes,
preferEphemeralSession: preferEphemeralSession,
),
);
// this code block demonstrates passing in values for the prompt parameter. in this case it prompts the user login even if they have already signed in. the list of supported values depends on the identity provider
// final AuthorizationTokenResponse result = await _appAuth.authorizeAndExchangeCode(
// AuthorizationTokenRequest(_clientId, _redirectUrl,
// serviceConfiguration: _serviceConfiguration,
// scopes: _scopes,
// promptValues: ['login']),
// );
if (result != null) {
_processAuthTokenResponse(result);
await _testApi(result);
}
} catch (_) {
_clearBusyState();
}
}
void _clearBusyState() {
setState(() {
_isBusy = false;
});
}
void _setBusyState() {
setState(() {
_isBusy = true;
});
}
void _processAuthTokenResponse(AuthorizationTokenResponse response) {
setState(() {
_accessToken = _accessTokenTextController.text = response.accessToken!;
_idToken = _idTokenTextController.text = response.idToken!;
_refreshToken = _refreshTokenTextController.text = response.refreshToken!;
_accessTokenExpirationTextController.text =
response.accessTokenExpirationDateTime!.toIso8601String();
});
}
void _processAuthResponse(AuthorizationResponse response) {
setState(() {
// save the code verifier and nonce as it must be used when exchanging the token
_codeVerifier = response.codeVerifier;
_nonce = response.nonce;
_authorizationCode =
_authorizationCodeTextController.text = response.authorizationCode!;
_isBusy = false;
});
}
void _processTokenResponse(TokenResponse? response) {
setState(() {
_accessToken = _accessTokenTextController.text = response!.accessToken!;
_idToken = _idTokenTextController.text = response.idToken!;
_refreshToken = _refreshTokenTextController.text = response.refreshToken!;
_accessTokenExpirationTextController.text =
response.accessTokenExpirationDateTime!.toIso8601String();
});
}
Future<void> _testApi(TokenResponse? response) async {
final http.Response httpResponse = await http.get(
Uri.parse('https://demo.duendesoftware.com/api/test'),
headers: <String, String>{'Authorization': 'Bearer $_accessToken'});
setState(() {
_userInfo = httpResponse.statusCode == 200 ? httpResponse.body : '';
_isBusy = false;
});
}
}
The screenshot would seem to indicate you are using an Android emulator that hasn't been properly configured or an outdated device. This is based on how the screenshot shows an webview being opened instead of Chrome Custom tab and webviews aren't supported by the native AppAuth SDKs. You'll need to use an up to date device or use a properly configured emulator. From memory, the emulator needs to be one with Google Play services enabled
The screenshot would seem to indicate you are using an Android emulator that hasn't been properly configured or an outdated device. This is based on how the screenshot shows an webview being opened instead of Chrome Custom tab and webviews aren't supported by the native AppAuth SDKs. You'll need to use an up to date device or use a properly configured emulator. From memory, the emulator needs to be one with Google Play services enabled
This also happens on real devices tested on android 12
E/AndroidRuntime(28156): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:584) E/AndroidRuntime(28156): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1034) E/AndroidRuntime(28156): Caused by: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity. E/AndroidRuntime(28156): at androidx.appcompat.app.AppCompatDelegateImpl.createSubDecor(AppCompatDelegateImpl.java:846) E/AndroidRuntime(28156): at androidx.appcompat.app.AppCompatDelegateImpl.ensureSubDecor(AppCompatDelegateImpl.java:809) E/AndroidRuntime(28156): at androidx.appcompat.app.AppCompatDelegateImpl.initWindowDecorActionBar(AppCompatDelegateImpl.java:550) E/AndroidRuntime(28156): at androidx.appcompat.app.AppCompatDelegateImpl.getSupportActionBar(AppCompatDelegateImpl.java:537) E/AndroidRuntime(28156): at androidx.appcompat.app.AppCompatDelegateImpl.invalidateOptionsMenu(AppCompatDelegateImpl.java:1220) E/AndroidRuntime(28156): at androidx.appcompat.app.AppCompatActivity.invalidateOptionsMenu(AppCompatActivity.java:314) E/AndroidRuntime(28156): at androidx.activity.ComponentActivity.invalidateMenu(ComponentActivity.java:553) E/AndroidRuntime(28156): at androidx.activity.ComponentActivity$$ExternalSyntheticLambda2.run(Unknown Source:2) E/AndroidRuntime(28156): at androidx.core.view.MenuHostHelper.addMenuProvider(MenuHostHelper.java:133) E/AndroidRuntime(28156): at androidx.activity.ComponentActivity.addMenuProvider(ComponentActivity.java:531) E/AndroidRuntime(28156): at androidx.fragment.app.FragmentActivity$HostCallbacks.addMenuProvider(FragmentActivity.java:736) E/AndroidRuntime(28156): at androidx.fragment.app.FragmentManager.attachController(FragmentManager.java:2784) E/AndroidRuntime(28156): at androidx.fragment.app.FragmentController.attachHost(FragmentController.java:117) E/AndroidRuntime(28156): at androidx.fragment.app.FragmentActivity.lambda$init$3$androidx-fragment-app-FragmentActivity(FragmentActivity.java:140) E/AndroidRuntime(28156): at androidx.fragment.app.FragmentActivity$$ExternalSyntheticLambda0.onContextAvailable(Unknown Source:2) E/AndroidRuntime(28156): at androidx.activity.contextaware.ContextAwareHelper.dispatchOnContextAvailable(ContextAwareHelper.java:99) E/AndroidRuntime(28156): at androidx.activity.ComponentActivity.onCreate(ComponentActivity.java:352) E/AndroidRuntime(28156): at androidx.fragment.app.FragmentActivity.onCreate(FragmentActivity.java:217) E/AndroidRuntime(28156): at net.openid.appauth.RedirectUriReceiverActivity.onCreate(RedirectUriReceiverActivity.java:49) E/AndroidRuntime(28156): at android.app.Activity.performCreate(Activity.java:8145) E/AndroidRuntime(28156): at android.app.Activity.performCreate(Activity.java:8125) E/AndroidRuntime(28156): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1344) E/AndroidRuntime(28156): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3750) E/AndroidRuntime(28156): ... 12 more I/Process (28156): Sending signal. PID: 28156 SIG: 9 Lost connection to device.