implement jwt flow

This commit is contained in:
Stefan Galler
2024-10-03 07:31:20 +02:00
parent 3da83e0abf
commit 7d41a7ed2d
48 changed files with 2562 additions and 10 deletions

6
.gitmodules vendored Normal file
View File

@ -0,0 +1,6 @@
[submodule "shelf-jwt-test-server"]
path = shelf-jwt-test-server
url = git@github.com:bettercoding-dev/shelf-jwt-test-server.git
[submodule "server/shelf-jwt-test-server"]
path = server/shelf-jwt-test-server
url = git@github.com:bettercoding-dev/shelf-jwt-test-server.git

View File

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View File

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

44
ios/Podfile Normal file
View File

@ -0,0 +1,44 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
use_modular_headers!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

22
ios/Podfile.lock Normal file
View File

@ -0,0 +1,22 @@
PODS:
- Flutter (1.0.0)
- flutter_secure_storage (3.3.1):
- Flutter
DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
EXTERNAL SOURCES:
Flutter:
:path: Flutter
flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
SPEC CHECKSUMS:
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
COCOAPODS: 1.15.2

View File

@ -8,6 +8,8 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
1646D1EEE5DF6FE5BAD39A15 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 91D9E65E8A5E264762FF8B1A /* Pods_RunnerTests.framework */; };
261FEC04840279EDAC19C313 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A02DCFF8620D3F72F79A3840 /* Pods_Runner.framework */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
@ -40,14 +42,19 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
132BB70143962167930A6E31 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
33E36FBF3F48DA58460C0E68 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
4E08E345669F7FAB9ABC4F0B /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
8F0EC2398A99C280A77C69B2 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
91D9E65E8A5E264762FF8B1A /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -55,13 +62,25 @@
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
A02DCFF8620D3F72F79A3840 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
C1A8494C902218FF87CAC30F /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
ED0E05E65940F20CD74F80A1 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
595AB5F2277E9FB394A4FBFD /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
1646D1EEE5DF6FE5BAD39A15 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
261FEC04840279EDAC19C313 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -94,6 +113,8 @@
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
A25A5221F0DECAE777A7C39A /* Pods */,
C3358ABD8B73863554D6CBCE /* Frameworks */,
);
sourceTree = "<group>";
};
@ -121,6 +142,28 @@
path = Runner;
sourceTree = "<group>";
};
A25A5221F0DECAE777A7C39A /* Pods */ = {
isa = PBXGroup;
children = (
8F0EC2398A99C280A77C69B2 /* Pods-Runner.debug.xcconfig */,
33E36FBF3F48DA58460C0E68 /* Pods-Runner.release.xcconfig */,
4E08E345669F7FAB9ABC4F0B /* Pods-Runner.profile.xcconfig */,
ED0E05E65940F20CD74F80A1 /* Pods-RunnerTests.debug.xcconfig */,
132BB70143962167930A6E31 /* Pods-RunnerTests.release.xcconfig */,
C1A8494C902218FF87CAC30F /* Pods-RunnerTests.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
C3358ABD8B73863554D6CBCE /* Frameworks */ = {
isa = PBXGroup;
children = (
A02DCFF8620D3F72F79A3840 /* Pods_Runner.framework */,
91D9E65E8A5E264762FF8B1A /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -128,8 +171,10 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
7EE5E162952D2EF1724B6F3C /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
595AB5F2277E9FB394A4FBFD /* Frameworks */,
);
buildRules = (
);
@ -145,12 +190,14 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
C1E76FD3B568DD3E0EEA9500 /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
26958E994E7C110BC936614B /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -222,6 +269,23 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
26958E994E7C110BC936614B /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@ -238,6 +302,28 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
7EE5E162952D2EF1724B6F3C /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
@ -253,6 +339,28 @@
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
C1E76FD3B568DD3E0EEA9500 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@ -362,7 +470,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 79C8VHVD3P;
DEVELOPMENT_TEAM = 78A8AZ76DR;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -379,6 +487,7 @@
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = ED0E05E65940F20CD74F80A1 /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@ -396,6 +505,7 @@
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 132BB70143962167930A6E31 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@ -411,6 +521,7 @@
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C1A8494C902218FF87CAC30F /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@ -542,7 +653,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 79C8VHVD3P;
DEVELOPMENT_TEAM = 78A8AZ76DR;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -565,7 +676,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 79C8VHVD3P;
DEVELOPMENT_TEAM = 78A8AZ76DR;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (

View File

@ -4,4 +4,7 @@
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,25 @@
import 'package:dio/dio.dart';
import 'package:retrofit/retrofit.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:flutter_jwt_auth/auth/client/login_request.dart';
import 'package:flutter_jwt_auth/auth/client/refresh_token_request.dart';
import 'package:flutter_jwt_auth/auth/model/auth_data.dart';
import 'package:flutter_jwt_auth/global_providers.dart';
part 'auth_client.g.dart';
@riverpod
AuthClient authClient(AuthClientRef ref) => AuthClient(
ref.watch(dioProvider),
);
@RestApi()
abstract class AuthClient {
factory AuthClient(Dio dio) = _AuthClient;
@POST('/login')
Future<AuthData> login(@Body() LoginRequest request);
@POST('/refresh')
Future<AuthData> refresh(@Body() RefreshTokenRequest request);
}

View File

@ -0,0 +1,140 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'auth_client.dart';
// **************************************************************************
// RetrofitGenerator
// **************************************************************************
// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element
class _AuthClient implements AuthClient {
_AuthClient(
this._dio, {
this.baseUrl,
this.errorLogger,
});
final Dio _dio;
String? baseUrl;
final ParseErrorLogger? errorLogger;
@override
Future<AuthData> login(LoginRequest request) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = request;
final _options = _setStreamType<AuthData>(Options(
method: 'POST',
headers: _headers,
extra: _extra,
)
.compose(
_dio.options,
'/login',
queryParameters: queryParameters,
data: _data,
)
.copyWith(
baseUrl: _combineBaseUrls(
_dio.options.baseUrl,
baseUrl,
)));
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late AuthData _value;
try {
_value = AuthData.fromJson(_result.data!);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
}
return _value;
}
@override
Future<AuthData> refresh(RefreshTokenRequest request) async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final _data = request;
final _options = _setStreamType<AuthData>(Options(
method: 'POST',
headers: _headers,
extra: _extra,
)
.compose(
_dio.options,
'/refresh',
queryParameters: queryParameters,
data: _data,
)
.copyWith(
baseUrl: _combineBaseUrls(
_dio.options.baseUrl,
baseUrl,
)));
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late AuthData _value;
try {
_value = AuthData.fromJson(_result.data!);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
}
return _value;
}
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
if (T != dynamic &&
!(requestOptions.responseType == ResponseType.bytes ||
requestOptions.responseType == ResponseType.stream)) {
if (T == String) {
requestOptions.responseType = ResponseType.plain;
} else {
requestOptions.responseType = ResponseType.json;
}
}
return requestOptions;
}
String _combineBaseUrls(
String dioBaseUrl,
String? baseUrl,
) {
if (baseUrl == null || baseUrl.trim().isEmpty) {
return dioBaseUrl;
}
final url = Uri.parse(baseUrl);
if (url.isAbsolute) {
return url.toString();
}
return Uri.parse(dioBaseUrl).resolveUri(url).toString();
}
}
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$authClientHash() => r'cd6faf293e6975a9091a92d7534d8e4a7d33284b';
/// See also [authClient].
@ProviderFor(authClient)
final authClientProvider = AutoDisposeProvider<AuthClient>.internal(
authClient,
name: r'authClientProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$authClientHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef AuthClientRef = AutoDisposeProviderRef<AuthClient>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@ -0,0 +1,15 @@
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'login_request.freezed.dart';
part 'login_request.g.dart';
@freezed
class LoginRequest with _$LoginRequest {
const factory LoginRequest({
required String username,
}) = _LoginRequest;
factory LoginRequest.fromJson(Map<String, dynamic> json) =>
_$LoginRequestFromJson(json);
}

View File

@ -0,0 +1,174 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'login_request.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
LoginRequest _$LoginRequestFromJson(Map<String, dynamic> json) {
return _LoginRequest.fromJson(json);
}
/// @nodoc
mixin _$LoginRequest {
String get username => throw _privateConstructorUsedError;
/// Serializes this LoginRequest to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of LoginRequest
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$LoginRequestCopyWith<LoginRequest> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $LoginRequestCopyWith<$Res> {
factory $LoginRequestCopyWith(
LoginRequest value, $Res Function(LoginRequest) then) =
_$LoginRequestCopyWithImpl<$Res, LoginRequest>;
@useResult
$Res call({String username});
}
/// @nodoc
class _$LoginRequestCopyWithImpl<$Res, $Val extends LoginRequest>
implements $LoginRequestCopyWith<$Res> {
_$LoginRequestCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of LoginRequest
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? username = null,
}) {
return _then(_value.copyWith(
username: null == username
? _value.username
: username // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
/// @nodoc
abstract class _$$LoginRequestImplCopyWith<$Res>
implements $LoginRequestCopyWith<$Res> {
factory _$$LoginRequestImplCopyWith(
_$LoginRequestImpl value, $Res Function(_$LoginRequestImpl) then) =
__$$LoginRequestImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String username});
}
/// @nodoc
class __$$LoginRequestImplCopyWithImpl<$Res>
extends _$LoginRequestCopyWithImpl<$Res, _$LoginRequestImpl>
implements _$$LoginRequestImplCopyWith<$Res> {
__$$LoginRequestImplCopyWithImpl(
_$LoginRequestImpl _value, $Res Function(_$LoginRequestImpl) _then)
: super(_value, _then);
/// Create a copy of LoginRequest
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? username = null,
}) {
return _then(_$LoginRequestImpl(
username: null == username
? _value.username
: username // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
@JsonSerializable()
class _$LoginRequestImpl with DiagnosticableTreeMixin implements _LoginRequest {
const _$LoginRequestImpl({required this.username});
factory _$LoginRequestImpl.fromJson(Map<String, dynamic> json) =>
_$$LoginRequestImplFromJson(json);
@override
final String username;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'LoginRequest(username: $username)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'LoginRequest'))
..add(DiagnosticsProperty('username', username));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$LoginRequestImpl &&
(identical(other.username, username) ||
other.username == username));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, username);
/// Create a copy of LoginRequest
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$LoginRequestImplCopyWith<_$LoginRequestImpl> get copyWith =>
__$$LoginRequestImplCopyWithImpl<_$LoginRequestImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$LoginRequestImplToJson(
this,
);
}
}
abstract class _LoginRequest implements LoginRequest {
const factory _LoginRequest({required final String username}) =
_$LoginRequestImpl;
factory _LoginRequest.fromJson(Map<String, dynamic> json) =
_$LoginRequestImpl.fromJson;
@override
String get username;
/// Create a copy of LoginRequest
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$LoginRequestImplCopyWith<_$LoginRequestImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,17 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'login_request.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$LoginRequestImpl _$$LoginRequestImplFromJson(Map<String, dynamic> json) =>
_$LoginRequestImpl(
username: json['username'] as String,
);
Map<String, dynamic> _$$LoginRequestImplToJson(_$LoginRequestImpl instance) =>
<String, dynamic>{
'username': instance.username,
};

View File

@ -0,0 +1,14 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'refresh_token_request.freezed.dart';
part 'refresh_token_request.g.dart';
@freezed
class RefreshTokenRequest with _$RefreshTokenRequest {
const factory RefreshTokenRequest({
required String token,
}) = _RefreshTokenRequest;
factory RefreshTokenRequest.fromJson(Map<String, dynamic> json) =>
_$RefreshTokenRequestFromJson(json);
}

View File

@ -0,0 +1,166 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'refresh_token_request.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
RefreshTokenRequest _$RefreshTokenRequestFromJson(Map<String, dynamic> json) {
return _RefreshTokenRequest.fromJson(json);
}
/// @nodoc
mixin _$RefreshTokenRequest {
String get token => throw _privateConstructorUsedError;
/// Serializes this RefreshTokenRequest to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of RefreshTokenRequest
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$RefreshTokenRequestCopyWith<RefreshTokenRequest> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $RefreshTokenRequestCopyWith<$Res> {
factory $RefreshTokenRequestCopyWith(
RefreshTokenRequest value, $Res Function(RefreshTokenRequest) then) =
_$RefreshTokenRequestCopyWithImpl<$Res, RefreshTokenRequest>;
@useResult
$Res call({String token});
}
/// @nodoc
class _$RefreshTokenRequestCopyWithImpl<$Res, $Val extends RefreshTokenRequest>
implements $RefreshTokenRequestCopyWith<$Res> {
_$RefreshTokenRequestCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of RefreshTokenRequest
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? token = null,
}) {
return _then(_value.copyWith(
token: null == token
? _value.token
: token // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
/// @nodoc
abstract class _$$RefreshTokenRequestImplCopyWith<$Res>
implements $RefreshTokenRequestCopyWith<$Res> {
factory _$$RefreshTokenRequestImplCopyWith(_$RefreshTokenRequestImpl value,
$Res Function(_$RefreshTokenRequestImpl) then) =
__$$RefreshTokenRequestImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String token});
}
/// @nodoc
class __$$RefreshTokenRequestImplCopyWithImpl<$Res>
extends _$RefreshTokenRequestCopyWithImpl<$Res, _$RefreshTokenRequestImpl>
implements _$$RefreshTokenRequestImplCopyWith<$Res> {
__$$RefreshTokenRequestImplCopyWithImpl(_$RefreshTokenRequestImpl _value,
$Res Function(_$RefreshTokenRequestImpl) _then)
: super(_value, _then);
/// Create a copy of RefreshTokenRequest
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? token = null,
}) {
return _then(_$RefreshTokenRequestImpl(
token: null == token
? _value.token
: token // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
@JsonSerializable()
class _$RefreshTokenRequestImpl implements _RefreshTokenRequest {
const _$RefreshTokenRequestImpl({required this.token});
factory _$RefreshTokenRequestImpl.fromJson(Map<String, dynamic> json) =>
_$$RefreshTokenRequestImplFromJson(json);
@override
final String token;
@override
String toString() {
return 'RefreshTokenRequest(token: $token)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$RefreshTokenRequestImpl &&
(identical(other.token, token) || other.token == token));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, token);
/// Create a copy of RefreshTokenRequest
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$RefreshTokenRequestImplCopyWith<_$RefreshTokenRequestImpl> get copyWith =>
__$$RefreshTokenRequestImplCopyWithImpl<_$RefreshTokenRequestImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$RefreshTokenRequestImplToJson(
this,
);
}
}
abstract class _RefreshTokenRequest implements RefreshTokenRequest {
const factory _RefreshTokenRequest({required final String token}) =
_$RefreshTokenRequestImpl;
factory _RefreshTokenRequest.fromJson(Map<String, dynamic> json) =
_$RefreshTokenRequestImpl.fromJson;
@override
String get token;
/// Create a copy of RefreshTokenRequest
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$RefreshTokenRequestImplCopyWith<_$RefreshTokenRequestImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,19 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'refresh_token_request.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$RefreshTokenRequestImpl _$$RefreshTokenRequestImplFromJson(
Map<String, dynamic> json) =>
_$RefreshTokenRequestImpl(
token: json['token'] as String,
);
Map<String, dynamic> _$$RefreshTokenRequestImplToJson(
_$RefreshTokenRequestImpl instance) =>
<String, dynamic>{
'token': instance.token,
};

View File

@ -0,0 +1,54 @@
import 'dart:developer';
import 'package:dio/dio.dart';
import 'package:flutter_jwt_auth/auth/state/auth_controller.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class AuthInterceptor extends Interceptor {
final Ref ref;
final Dio dio;
const AuthInterceptor(this.ref, this.dio);
@override
void onRequest(
RequestOptions options, RequestInterceptorHandler handler) async {
final authData = await ref.read(authControllerProvider.future);
final token = authData?.token;
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
super.onRequest(options, handler);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
final isRetry = err.requestOptions.extra['isRetry'] == true;
// if response is unauthorized
if (err.response?.statusCode == 401 && !isRetry) {
try {
final authData = await ref.read(authControllerProvider.future);
final refreshToken = authData?.refreshToken;
if (refreshToken != null) {
await ref.read(authControllerProvider.notifier).refreshToken();
final options = err.requestOptions;
options.extra['isRetry'] = true;
final response = await dio.fetch(options);
handler.resolve(response);
} else {
super.onError(err, handler);
}
} on DioException catch (error, trace) {
log('cannot refresh', error: error, stackTrace: trace);
super.onError(error, handler);
}
} else {
super.onError(err, handler);
}
}
}

View File

@ -0,0 +1,16 @@
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'auth_data.freezed.dart';
part 'auth_data.g.dart';
@freezed
class AuthData with _$AuthData {
const factory AuthData({
required String token,
required String refreshToken,
}) = _AuthData;
factory AuthData.fromJson(Map<String, dynamic> json) =>
_$AuthDataFromJson(json);
}

View File

@ -0,0 +1,191 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'auth_data.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
AuthData _$AuthDataFromJson(Map<String, dynamic> json) {
return _AuthData.fromJson(json);
}
/// @nodoc
mixin _$AuthData {
String get token => throw _privateConstructorUsedError;
String get refreshToken => throw _privateConstructorUsedError;
/// Serializes this AuthData to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of AuthData
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$AuthDataCopyWith<AuthData> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $AuthDataCopyWith<$Res> {
factory $AuthDataCopyWith(AuthData value, $Res Function(AuthData) then) =
_$AuthDataCopyWithImpl<$Res, AuthData>;
@useResult
$Res call({String token, String refreshToken});
}
/// @nodoc
class _$AuthDataCopyWithImpl<$Res, $Val extends AuthData>
implements $AuthDataCopyWith<$Res> {
_$AuthDataCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of AuthData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? token = null,
Object? refreshToken = null,
}) {
return _then(_value.copyWith(
token: null == token
? _value.token
: token // ignore: cast_nullable_to_non_nullable
as String,
refreshToken: null == refreshToken
? _value.refreshToken
: refreshToken // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
/// @nodoc
abstract class _$$AuthDataImplCopyWith<$Res>
implements $AuthDataCopyWith<$Res> {
factory _$$AuthDataImplCopyWith(
_$AuthDataImpl value, $Res Function(_$AuthDataImpl) then) =
__$$AuthDataImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String token, String refreshToken});
}
/// @nodoc
class __$$AuthDataImplCopyWithImpl<$Res>
extends _$AuthDataCopyWithImpl<$Res, _$AuthDataImpl>
implements _$$AuthDataImplCopyWith<$Res> {
__$$AuthDataImplCopyWithImpl(
_$AuthDataImpl _value, $Res Function(_$AuthDataImpl) _then)
: super(_value, _then);
/// Create a copy of AuthData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? token = null,
Object? refreshToken = null,
}) {
return _then(_$AuthDataImpl(
token: null == token
? _value.token
: token // ignore: cast_nullable_to_non_nullable
as String,
refreshToken: null == refreshToken
? _value.refreshToken
: refreshToken // ignore: cast_nullable_to_non_nullable
as String,
));
}
}
/// @nodoc
@JsonSerializable()
class _$AuthDataImpl with DiagnosticableTreeMixin implements _AuthData {
const _$AuthDataImpl({required this.token, required this.refreshToken});
factory _$AuthDataImpl.fromJson(Map<String, dynamic> json) =>
_$$AuthDataImplFromJson(json);
@override
final String token;
@override
final String refreshToken;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'AuthData(token: $token, refreshToken: $refreshToken)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'AuthData'))
..add(DiagnosticsProperty('token', token))
..add(DiagnosticsProperty('refreshToken', refreshToken));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$AuthDataImpl &&
(identical(other.token, token) || other.token == token) &&
(identical(other.refreshToken, refreshToken) ||
other.refreshToken == refreshToken));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, token, refreshToken);
/// Create a copy of AuthData
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$AuthDataImplCopyWith<_$AuthDataImpl> get copyWith =>
__$$AuthDataImplCopyWithImpl<_$AuthDataImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$AuthDataImplToJson(
this,
);
}
}
abstract class _AuthData implements AuthData {
const factory _AuthData(
{required final String token,
required final String refreshToken}) = _$AuthDataImpl;
factory _AuthData.fromJson(Map<String, dynamic> json) =
_$AuthDataImpl.fromJson;
@override
String get token;
@override
String get refreshToken;
/// Create a copy of AuthData
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$AuthDataImplCopyWith<_$AuthDataImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,19 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'auth_data.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$AuthDataImpl _$$AuthDataImplFromJson(Map<String, dynamic> json) =>
_$AuthDataImpl(
token: json['token'] as String,
refreshToken: json['refreshToken'] as String,
);
Map<String, dynamic> _$$AuthDataImplToJson(_$AuthDataImpl instance) =>
<String, dynamic>{
'token': instance.token,
'refreshToken': instance.refreshToken,
};

View File

@ -0,0 +1,23 @@
import 'package:flutter_jwt_auth/auth/client/auth_client.dart';
import 'package:flutter_jwt_auth/auth/client/login_request.dart';
import 'package:flutter_jwt_auth/auth/client/refresh_token_request.dart';
import 'package:flutter_jwt_auth/auth/model/auth_data.dart';
import 'package:flutter_jwt_auth/auth/repository/auth_repository.dart';
class ApiAuthRepository implements AuthRepository {
final AuthClient authClient;
const ApiAuthRepository(this.authClient);
@override
Future<AuthData> login(String username) {
final request = LoginRequest(username: username);
return authClient.login(request);
}
@override
Future<AuthData> refreshToken(String token) {
final request = RefreshTokenRequest(token: token);
return authClient.refresh(request);
}
}

View File

@ -0,0 +1,17 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:flutter_jwt_auth/auth/client/auth_client.dart';
import 'package:flutter_jwt_auth/auth/model/auth_data.dart';
import 'package:flutter_jwt_auth/auth/repository/api_auth_repository.dart';
part 'auth_repository.g.dart';
@riverpod
AuthRepository authRepository(AuthRepositoryRef ref) => ApiAuthRepository(
ref.watch(authClientProvider),
);
abstract interface class AuthRepository {
Future<AuthData> login(String username);
Future<AuthData> refreshToken(String token);
}

View File

@ -0,0 +1,25 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'auth_repository.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$authRepositoryHash() => r'0eb6db9e8bb6c8d101d6d31065b02e0d53114782';
/// See also [authRepository].
@ProviderFor(authRepository)
final authRepositoryProvider = AutoDisposeProvider<AuthRepository>.internal(
authRepository,
name: r'authRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$authRepositoryHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef AuthRepositoryRef = AutoDisposeProviderRef<AuthRepository>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@ -0,0 +1,63 @@
import 'package:dio/dio.dart';
import 'package:flutter_jwt_auth/auth/model/auth_data.dart';
import 'package:flutter_jwt_auth/auth/repository/auth_repository.dart';
import 'package:flutter_jwt_auth/common/repository/storage_repository.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'auth_controller.g.dart';
@riverpod
class AuthController extends _$AuthController {
@override
Future<AuthData?> build() async {
final storageRepository = ref.watch(storageRepositoryProvider);
final token = await storageRepository.getToken();
final refreshToken = await storageRepository.getRefreshToken();
if (token != null && refreshToken != null) {
return AuthData(token: token, refreshToken: refreshToken);
} else {
return null;
}
}
Future<void> login(String username) async {
final authRepository = ref.read(authRepositoryProvider);
final storageRepository = ref.read(storageRepositoryProvider);
state = await AsyncValue.guard(() async {
final authData = await authRepository.login(username);
await storageRepository.storeToken(authData.token);
await storageRepository.storeRefreshToken(authData.refreshToken);
return authData;
});
}
Future<void> refreshToken() async {
final authRepository = ref.read(authRepositoryProvider);
final storageRepository = ref.read(storageRepositoryProvider);
final authData = state.valueOrNull;
if (authData != null) {
try {
final newTokens =
await authRepository.refreshToken(authData.refreshToken);
storageRepository.storeToken(newTokens.token);
storageRepository.storeRefreshToken(newTokens.refreshToken);
state = AsyncData(newTokens);
} on DioException {
await logout();
rethrow;
}
}
}
Future<void> logout() async {
state = await AsyncValue.guard(() async {
final storageRepository = ref.read(storageRepositoryProvider);
await storageRepository.deleteToken();
await storageRepository.deleteRefreshToken();
return null;
});
}
}

View File

@ -0,0 +1,26 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'auth_controller.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$authControllerHash() => r'413fe66c0b164847c772c71ea7dfd1270ffe7672';
/// See also [AuthController].
@ProviderFor(AuthController)
final authControllerProvider =
AutoDisposeAsyncNotifierProvider<AuthController, AuthData?>.internal(
AuthController.new,
name: r'authControllerProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$authControllerHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$AuthController = AutoDisposeAsyncNotifier<AuthData?>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@ -0,0 +1,33 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_jwt_auth/common/repository/storage_repository.dart';
class SecureStorageRepository implements StorageRepository {
static const String _keyToken = 'token';
static const String _keyRefreshToken = 'refresh_token';
final FlutterSecureStorage storage;
const SecureStorageRepository({required this.storage});
// Token
@override
Future<String?> getToken() => storage.read(key: _keyToken);
@override
Future<void> storeToken(String token) =>
storage.write(key: _keyToken, value: token);
@override
Future<void> deleteToken() => storage.delete(key: _keyToken);
// Refresh Token
@override
Future<String?> getRefreshToken() => storage.read(key: _keyRefreshToken);
@override
Future<void> storeRefreshToken(String token) =>
storage.write(key: _keyRefreshToken, value: token);
@override
Future<void> deleteRefreshToken() => storage.delete(key: _keyRefreshToken);
}

View File

@ -0,0 +1,25 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:flutter_jwt_auth/common/repository/secure_storage_repository.dart';
import 'package:flutter_jwt_auth/global_providers.dart';
part 'storage_repository.g.dart';
@riverpod
StorageRepository storageRepository(StorageRepositoryRef ref) =>
SecureStorageRepository(
storage: ref.watch(flutterSecureStorageProvider),
);
abstract interface class StorageRepository {
Future<void> storeToken(String token);
Future<void> storeRefreshToken(String token);
Future<String?> getToken();
Future<String?> getRefreshToken();
Future<void> deleteToken();
Future<void> deleteRefreshToken();
}

View File

@ -0,0 +1,26 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'storage_repository.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$storageRepositoryHash() => r'aa1e363c047d2b2ebca478ea1408adff6b8095e6';
/// See also [storageRepository].
@ProviderFor(storageRepository)
final storageRepositoryProvider =
AutoDisposeProvider<StorageRepository>.internal(
storageRepository,
name: r'storageRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$storageRepositoryHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef StorageRepositoryRef = AutoDisposeProviderRef<StorageRepository>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@ -0,0 +1,33 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
class ErrorView extends StatelessWidget {
final Object? error;
const ErrorView({
super.key,
required this.error,
});
@override
Widget build(BuildContext context) {
final error = this.error;
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Error',
style: Theme.of(context).textTheme.titleMedium,
),
if (error is DioException) ...[
Text('Status Code: ${error.response?.statusCode}'),
Text(error.response?.statusMessage ?? '')
] else
Text(error.toString())
],
),
);
}
}

View File

@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
class LoadingView extends StatelessWidget {
const LoadingView({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: CircularProgressIndicator(),
);
}
}

26
lib/global_providers.dart Normal file
View File

@ -0,0 +1,26 @@
import 'package:dio/dio.dart';
import 'package:flutter_jwt_auth/auth/interceptor/auth_interceptor.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'global_providers.g.dart';
@riverpod
Dio dio(DioRef ref) {
final dio = Dio(
BaseOptions(
baseUrl: 'http://localhost:8080',
contentType: Headers.jsonContentType,
),
);
dio.interceptors.add(PrettyDioLogger());
dio.interceptors.add(AuthInterceptor(ref, dio));
return dio;
}
@riverpod
FlutterSecureStorage flutterSecureStorage(FlutterSecureStorageRef ref) =>
const FlutterSecureStorage();

View File

@ -0,0 +1,41 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'global_providers.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$dioHash() => r'9347067b2b996a62b03e4531bcbd98f9fa468a91';
/// See also [dio].
@ProviderFor(dio)
final dioProvider = AutoDisposeProvider<Dio>.internal(
dio,
name: r'dioProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$dioHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef DioRef = AutoDisposeProviderRef<Dio>;
String _$flutterSecureStorageHash() =>
r'f573db2722d185024bf535e0b496b1442ddf2db7';
/// See also [flutterSecureStorage].
@ProviderFor(flutterSecureStorage)
final flutterSecureStorageProvider =
AutoDisposeProvider<FlutterSecureStorage>.internal(
flutterSecureStorage,
name: r'flutterSecureStorageProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$flutterSecureStorageHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef FlutterSecureStorageRef = AutoDisposeProviderRef<FlutterSecureStorage>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_jwt_auth/time/ui/time_page.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(const MainApp());
runApp(const ProviderScope(child: MainApp()));
}
class MainApp extends StatelessWidget {
@ -10,11 +12,8 @@ class MainApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: Text('Hello World!'),
),
),
debugShowCheckedModeBanner: false,
home: TimePage(),
);
}
}

View File

@ -0,0 +1,20 @@
import 'package:dio/dio.dart';
import 'package:retrofit/retrofit.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:flutter_jwt_auth/global_providers.dart';
import 'package:flutter_jwt_auth/time/client/time_response.dart';
part 'time_client.g.dart';
@riverpod
TimeClient timeClient(TimeClientRef ref) => TimeClient(
ref.watch(dioProvider),
);
@RestApi()
abstract class TimeClient {
factory TimeClient(Dio dio) = _TimeClient;
@GET('/time')
Future<TimeResponse> getServerTime();
}

View File

@ -0,0 +1,107 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'time_client.dart';
// **************************************************************************
// RetrofitGenerator
// **************************************************************************
// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers,unused_element
class _TimeClient implements TimeClient {
_TimeClient(
this._dio, {
this.baseUrl,
this.errorLogger,
});
final Dio _dio;
String? baseUrl;
final ParseErrorLogger? errorLogger;
@override
Future<TimeResponse> getServerTime() async {
final _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
const Map<String, dynamic>? _data = null;
final _options = _setStreamType<TimeResponse>(Options(
method: 'GET',
headers: _headers,
extra: _extra,
)
.compose(
_dio.options,
'/time',
queryParameters: queryParameters,
data: _data,
)
.copyWith(
baseUrl: _combineBaseUrls(
_dio.options.baseUrl,
baseUrl,
)));
final _result = await _dio.fetch<Map<String, dynamic>>(_options);
late TimeResponse _value;
try {
_value = TimeResponse.fromJson(_result.data!);
} on Object catch (e, s) {
errorLogger?.logError(e, s, _options);
rethrow;
}
return _value;
}
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
if (T != dynamic &&
!(requestOptions.responseType == ResponseType.bytes ||
requestOptions.responseType == ResponseType.stream)) {
if (T == String) {
requestOptions.responseType = ResponseType.plain;
} else {
requestOptions.responseType = ResponseType.json;
}
}
return requestOptions;
}
String _combineBaseUrls(
String dioBaseUrl,
String? baseUrl,
) {
if (baseUrl == null || baseUrl.trim().isEmpty) {
return dioBaseUrl;
}
final url = Uri.parse(baseUrl);
if (url.isAbsolute) {
return url.toString();
}
return Uri.parse(dioBaseUrl).resolveUri(url).toString();
}
}
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$timeClientHash() => r'6b646e6d22b33365b77143253c69787fea6889b3';
/// See also [timeClient].
@ProviderFor(timeClient)
final timeClientProvider = AutoDisposeProvider<TimeClient>.internal(
timeClient,
name: r'timeClientProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$timeClientHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef TimeClientRef = AutoDisposeProviderRef<TimeClient>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@ -0,0 +1,15 @@
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'time_response.freezed.dart';
part 'time_response.g.dart';
@freezed
class TimeResponse with _$TimeResponse {
const factory TimeResponse({
required DateTime time,
}) = _TimeResponse;
factory TimeResponse.fromJson(Map<String, dynamic> json) =>
_$TimeResponseFromJson(json);
}

View File

@ -0,0 +1,173 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'time_response.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
TimeResponse _$TimeResponseFromJson(Map<String, dynamic> json) {
return _TimeResponse.fromJson(json);
}
/// @nodoc
mixin _$TimeResponse {
DateTime get time => throw _privateConstructorUsedError;
/// Serializes this TimeResponse to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of TimeResponse
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$TimeResponseCopyWith<TimeResponse> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $TimeResponseCopyWith<$Res> {
factory $TimeResponseCopyWith(
TimeResponse value, $Res Function(TimeResponse) then) =
_$TimeResponseCopyWithImpl<$Res, TimeResponse>;
@useResult
$Res call({DateTime time});
}
/// @nodoc
class _$TimeResponseCopyWithImpl<$Res, $Val extends TimeResponse>
implements $TimeResponseCopyWith<$Res> {
_$TimeResponseCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of TimeResponse
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? time = null,
}) {
return _then(_value.copyWith(
time: null == time
? _value.time
: time // ignore: cast_nullable_to_non_nullable
as DateTime,
) as $Val);
}
}
/// @nodoc
abstract class _$$TimeResponseImplCopyWith<$Res>
implements $TimeResponseCopyWith<$Res> {
factory _$$TimeResponseImplCopyWith(
_$TimeResponseImpl value, $Res Function(_$TimeResponseImpl) then) =
__$$TimeResponseImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({DateTime time});
}
/// @nodoc
class __$$TimeResponseImplCopyWithImpl<$Res>
extends _$TimeResponseCopyWithImpl<$Res, _$TimeResponseImpl>
implements _$$TimeResponseImplCopyWith<$Res> {
__$$TimeResponseImplCopyWithImpl(
_$TimeResponseImpl _value, $Res Function(_$TimeResponseImpl) _then)
: super(_value, _then);
/// Create a copy of TimeResponse
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? time = null,
}) {
return _then(_$TimeResponseImpl(
time: null == time
? _value.time
: time // ignore: cast_nullable_to_non_nullable
as DateTime,
));
}
}
/// @nodoc
@JsonSerializable()
class _$TimeResponseImpl with DiagnosticableTreeMixin implements _TimeResponse {
const _$TimeResponseImpl({required this.time});
factory _$TimeResponseImpl.fromJson(Map<String, dynamic> json) =>
_$$TimeResponseImplFromJson(json);
@override
final DateTime time;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'TimeResponse(time: $time)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'TimeResponse'))
..add(DiagnosticsProperty('time', time));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$TimeResponseImpl &&
(identical(other.time, time) || other.time == time));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, time);
/// Create a copy of TimeResponse
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$TimeResponseImplCopyWith<_$TimeResponseImpl> get copyWith =>
__$$TimeResponseImplCopyWithImpl<_$TimeResponseImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$TimeResponseImplToJson(
this,
);
}
}
abstract class _TimeResponse implements TimeResponse {
const factory _TimeResponse({required final DateTime time}) =
_$TimeResponseImpl;
factory _TimeResponse.fromJson(Map<String, dynamic> json) =
_$TimeResponseImpl.fromJson;
@override
DateTime get time;
/// Create a copy of TimeResponse
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$TimeResponseImplCopyWith<_$TimeResponseImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,17 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'time_response.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$TimeResponseImpl _$$TimeResponseImplFromJson(Map<String, dynamic> json) =>
_$TimeResponseImpl(
time: DateTime.parse(json['time'] as String),
);
Map<String, dynamic> _$$TimeResponseImplToJson(_$TimeResponseImpl instance) =>
<String, dynamic>{
'time': instance.time.toIso8601String(),
};

View File

@ -0,0 +1,19 @@
import 'package:dio/dio.dart';
import 'package:flutter_jwt_auth/time/client/time_client.dart';
import 'package:flutter_jwt_auth/time/repository/time_repository.dart';
class ApiTimeRepository implements TimeRepository {
final TimeClient timeClient;
const ApiTimeRepository(this.timeClient);
@override
Future<DateTime> getServerTime() async {
try {
final response = await timeClient.getServerTime();
return response.time;
} on DioException {
rethrow;
}
}
}

View File

@ -0,0 +1,10 @@
import 'package:flutter_jwt_auth/time/repository/time_repository.dart';
class MockTimeRepository implements TimeRepository {
@override
Future<DateTime> getServerTime() async {
// simulate api delay
await Future.delayed(const Duration(milliseconds: 500));
return DateTime.now();
}
}

View File

@ -0,0 +1,13 @@
import 'package:flutter_jwt_auth/time/client/time_client.dart';
import 'package:flutter_jwt_auth/time/repository/api_time_repository.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'time_repository.g.dart';
@riverpod
TimeRepository timeRepository(TimeRepositoryRef ref) =>
ApiTimeRepository(ref.watch(timeClientProvider));
abstract interface class TimeRepository {
Future<DateTime> getServerTime();
}

View File

@ -0,0 +1,25 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'time_repository.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$timeRepositoryHash() => r'cbbdb501669190c470f58ab771d36c8fb5fccdd7';
/// See also [timeRepository].
@ProviderFor(timeRepository)
final timeRepositoryProvider = AutoDisposeProvider<TimeRepository>.internal(
timeRepository,
name: r'timeRepositoryProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$timeRepositoryHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef TimeRepositoryRef = AutoDisposeProviderRef<TimeRepository>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@ -0,0 +1,13 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:flutter_jwt_auth/time/repository/time_repository.dart';
part 'time_controller.g.dart';
@riverpod
class TimeController extends _$TimeController {
@override
Future<DateTime> build() {
final repository = ref.watch(timeRepositoryProvider);
return repository.getServerTime();
}
}

View File

@ -0,0 +1,26 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'time_controller.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$timeControllerHash() => r'f9d6f7e9fd0adcfde0b8efa7da793670b7a0ddae';
/// See also [TimeController].
@ProviderFor(TimeController)
final timeControllerProvider =
AutoDisposeAsyncNotifierProvider<TimeController, DateTime>.internal(
TimeController.new,
name: r'timeControllerProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$timeControllerHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$TimeController = AutoDisposeAsyncNotifier<DateTime>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_jwt_auth/auth/state/auth_controller.dart';
class LoginButton extends ConsumerWidget {
const LoginButton({super.key});
_login(WidgetRef ref) {
ref.read(authControllerProvider.notifier).login('MyUsername');
}
_logout(WidgetRef ref) {
ref.read(authControllerProvider.notifier).logout();
}
@override
Widget build(BuildContext context, WidgetRef ref) {
final authState = ref.watch(authControllerProvider);
final isAuthenticated = authState.valueOrNull != null;
return TextButton(
onPressed: () => isAuthenticated ? _logout(ref) : _login(ref),
child: Text(
isAuthenticated ? 'Log out' : 'Log in',
),
);
}
}

View File

@ -1 +1,102 @@
import 'package:flutter/material.dart';
import 'package:flutter_jwt_auth/auth/state/auth_controller.dart';
import 'package:flutter_jwt_auth/common/ui/error_view.dart';
import 'package:flutter_jwt_auth/common/ui/loading_view.dart';
import 'package:flutter_jwt_auth/time/state/time_controller.dart';
import 'package:flutter_jwt_auth/time/ui/login_button.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class TimePage extends ConsumerWidget {
const TimePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
ref.listen(authControllerProvider, (prev, state) {
if (state.hasError) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
backgroundColor: Colors.red,
content: Text('Login error occurred'),
),
);
}
});
return Scaffold(
appBar: AppBar(
title: const Text('JWT Auth Tutorial'),
actions: const [
LoginButton(),
],
),
body: const Column(
children: [
_AuthContent(),
_TimeContent(),
],
),
);
}
}
class _AuthContent extends ConsumerWidget {
const _AuthContent();
@override
Widget build(BuildContext context, WidgetRef ref) {
final authState = ref.watch(authControllerProvider);
return authState.when(
data: (authData) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ListTile(
subtitle: const Text('Token'),
title: Text(authData?.token ?? ''),
),
ListTile(
subtitle: const Text('Refresh Token'),
title: Text(authData?.refreshToken ?? ''),
),
],
),
error: (error, trace) => Center(
child: Text(error.toString()),
),
loading: () => const LoadingView(),
);
}
}
class _TimeContent extends ConsumerWidget {
const _TimeContent();
@override
Widget build(BuildContext context, WidgetRef ref) {
final isAuthorized = ref.watch(authControllerProvider).valueOrNull != null;
if (isAuthorized) {
final timeState = ref.watch(timeControllerProvider);
return timeState.when(
data: (time) => Column(
children: [
ListTile(
subtitle: const Text('Current Server Time'),
title: Text(time.toString()),
),
ElevatedButton(
onPressed: () => ref.refresh(timeControllerProvider.future),
child: const Text('Refresh'),
),
],
),
error: (error, trace) => ErrorView(error: error),
loading: () => const LoadingView(),
);
} else {
return const Center(
child: Text('Unauthorized'),
);
}
}
}

View File

@ -1,6 +1,43 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
url: "https://pub.dev"
source: hosted
version: "72.0.0"
_macros:
dependency: transitive
description: dart
source: sdk
version: "0.3.2"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
url: "https://pub.dev"
source: hosted
version: "6.7.0"
analyzer_plugin:
dependency: transitive
description:
name: analyzer_plugin
sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161"
url: "https://pub.dev"
source: hosted
version: "0.11.3"
args:
dependency: transitive
description:
name: args
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
url: "https://pub.dev"
source: hosted
version: "2.5.0"
async:
dependency: transitive
description:
@ -17,6 +54,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
build:
dependency: transitive
description:
name: build
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
build_config:
dependency: transitive
description:
name: build_config
sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
url: "https://pub.dev"
source: hosted
version: "1.1.1"
build_daemon:
dependency: transitive
description:
name: build_daemon
sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
url: "https://pub.dev"
source: hosted
version: "2.4.2"
build_runner:
dependency: "direct dev"
description:
name: build_runner
sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04
url: "https://pub.dev"
source: hosted
version: "2.4.12"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0
url: "https://pub.dev"
source: hosted
version: "7.3.2"
built_collection:
dependency: transitive
description:
name: built_collection
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
url: "https://pub.dev"
source: hosted
version: "5.1.1"
built_value:
dependency: transitive
description:
name: built_value
sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb
url: "https://pub.dev"
source: hosted
version: "8.9.2"
characters:
dependency: transitive
description:
@ -25,6 +126,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev"
source: hosted
version: "2.0.3"
ci:
dependency: transitive
description:
name: ci
sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13"
url: "https://pub.dev"
source: hosted
version: "0.1.0"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
url: "https://pub.dev"
source: hosted
version: "0.4.1"
clock:
dependency: transitive
description:
@ -33,6 +158,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.1"
code_builder:
dependency: transitive
description:
name: code_builder
sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37
url: "https://pub.dev"
source: hosted
version: "4.10.0"
collection:
dependency: transitive
description:
@ -41,6 +174,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.18.0"
convert:
dependency: transitive
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27
url: "https://pub.dev"
source: hosted
version: "3.0.5"
custom_lint:
dependency: "direct dev"
description:
name: custom_lint
sha256: "6e1ec47427ca968f22bce734d00028ae7084361999b41673291138945c5baca0"
url: "https://pub.dev"
source: hosted
version: "0.6.7"
custom_lint_builder:
dependency: transitive
description:
name: custom_lint_builder
sha256: ba2f90fff4eff71d202d097eb14b14f87087eaaef742e956208c0eb9d3a40a21
url: "https://pub.dev"
source: hosted
version: "0.6.7"
custom_lint_core:
dependency: transitive
description:
name: custom_lint_core
sha256: "4ddbbdaa774265de44c97054dcec058a83d9081d071785ece601e348c18c267d"
url: "https://pub.dev"
source: hosted
version: "0.6.5"
dart_style:
dependency: transitive
description:
name: dart_style
sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab"
url: "https://pub.dev"
source: hosted
version: "2.3.7"
dio:
dependency: "direct main"
description:
name: dio
sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260"
url: "https://pub.dev"
source: hosted
version: "5.7.0"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
fake_async:
dependency: transitive
description:
@ -49,6 +246,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
file:
dependency: transitive
description:
name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
flutter:
dependency: "direct main"
description: flutter
@ -62,11 +275,123 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.0"
flutter_riverpod:
dependency: "direct main"
description:
name: flutter_riverpod
sha256: "0f1974eff5bbe774bf1d870e406fc6f29e3d6f1c46bd9c58e7172ff68a785d7d"
url: "https://pub.dev"
source: hosted
version: "2.5.1"
flutter_secure_storage:
dependency: "direct main"
description:
name: flutter_secure_storage
sha256: "9f3dd2ac3b6875b0fde5b04734789c3ef35ba3965c18e99dd564a7a2f8056df6"
url: "https://pub.dev"
source: hosted
version: "4.2.1"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
freezed:
dependency: "direct dev"
description:
name: freezed
sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e"
url: "https://pub.dev"
source: hosted
version: "2.5.7"
freezed_annotation:
dependency: "direct main"
description:
name: freezed_annotation
sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2
url: "https://pub.dev"
source: hosted
version: "2.4.4"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
url: "https://pub.dev"
source: hosted
version: "4.0.0"
glob:
dependency: transitive
description:
name: glob
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
graphs:
dependency: transitive
description:
name: graphs
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
hotreloader:
dependency: transitive
description:
name: hotreloader
sha256: ed56fdc1f3a8ac924e717257621d09e9ec20e308ab6352a73a50a1d7a4d9158e
url: "https://pub.dev"
source: hosted
version: "4.2.0"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
io:
dependency: transitive
description:
name: io
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
js:
dependency: transitive
description:
name: js
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
url: "https://pub.dev"
source: hosted
version: "0.7.1"
json_annotation:
dependency: "direct main"
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.9.0"
json_serializable:
dependency: "direct dev"
description:
name: json_serializable
sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b
url: "https://pub.dev"
source: hosted
version: "6.8.0"
leak_tracker:
dependency: transitive
description:
@ -99,6 +424,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.0"
logging:
dependency: transitive
description:
name: logging
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
macros:
dependency: transitive
description:
name: macros
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
url: "https://pub.dev"
source: hosted
version: "0.1.2-main.4"
matcher:
dependency: transitive
description:
@ -123,6 +464,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.15.0"
mime:
dependency: transitive
description:
name: mime
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
url: "https://pub.dev"
source: hosted
version: "1.0.6"
package_config:
dependency: transitive
description:
name: package_config
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
path:
dependency: transitive
description:
@ -131,11 +488,147 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.9.0"
pool:
dependency: transitive
description:
name: pool
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
url: "https://pub.dev"
source: hosted
version: "1.5.1"
pretty_dio_logger:
dependency: "direct main"
description:
name: pretty_dio_logger
sha256: "36f2101299786d567869493e2f5731de61ce130faa14679473b26905a92b6407"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
protobuf:
dependency: transitive
description:
name: protobuf
sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8
url: "https://pub.dev"
source: hosted
version: "1.3.0"
retrofit:
dependency: "direct main"
description:
name: retrofit
sha256: "3c9885ef3dbc5dc4b3fb0a40c972ab52e4dad04d52dac9bba24dfa76cf100451"
url: "https://pub.dev"
source: hosted
version: "4.4.1"
retrofit_generator:
dependency: "direct dev"
description:
name: retrofit_generator
sha256: f20982392512587b1cce6cd34e3cf953a01a3fe8f82e4c34e10afe65e70c3556
url: "https://pub.dev"
source: hosted
version: "9.1.2"
riverpod:
dependency: transitive
description:
name: riverpod
sha256: f21b32ffd26a36555e501b04f4a5dca43ed59e16343f1a30c13632b2351dfa4d
url: "https://pub.dev"
source: hosted
version: "2.5.1"
riverpod_analyzer_utils:
dependency: transitive
description:
name: riverpod_analyzer_utils
sha256: ac28d7bc678471ec986b42d88e5a0893513382ff7542c7ac9634463b044ac72c
url: "https://pub.dev"
source: hosted
version: "0.5.4"
riverpod_annotation:
dependency: "direct main"
description:
name: riverpod_annotation
sha256: e5e796c0eba4030c704e9dae1b834a6541814963292839dcf9638d53eba84f5c
url: "https://pub.dev"
source: hosted
version: "2.3.5"
riverpod_generator:
dependency: "direct dev"
description:
name: riverpod_generator
sha256: "63311e361ffc578d655dfc31b48dfa4ed3bc76fd06f9be845e9bf97c5c11a429"
url: "https://pub.dev"
source: hosted
version: "2.4.3"
riverpod_lint:
dependency: "direct dev"
description:
name: riverpod_lint
sha256: a35a92f2c2a4b7a5d95671c96c5432b42c20f26bb3e985e83d0b186471b61a85
url: "https://pub.dev"
source: hosted
version: "2.3.13"
rxdart:
dependency: transitive
description:
name: rxdart
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
url: "https://pub.dev"
source: hosted
version: "0.28.0"
shelf:
dependency: transitive
description:
name: shelf
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
url: "https://pub.dev"
source: hosted
version: "1.4.1"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_gen:
dependency: transitive
description:
name: source_gen
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
url: "https://pub.dev"
source: hosted
version: "1.5.0"
source_helper:
dependency: transitive
description:
name: source_helper
sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd"
url: "https://pub.dev"
source: hosted
version: "1.3.4"
source_span:
dependency: transitive
description:
@ -144,6 +637,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.10.0"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
stack_trace:
dependency: transitive
description:
@ -152,6 +653,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.11.1"
state_notifier:
dependency: transitive
description:
name: state_notifier
sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb
url: "https://pub.dev"
source: hosted
version: "1.0.0"
stream_channel:
dependency: transitive
description:
@ -160,6 +669,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.2"
stream_transform:
dependency: transitive
description:
name: stream_transform
sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
string_scanner:
dependency: transitive
description:
@ -184,6 +701,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.2"
timing:
dependency: transitive
description:
name: timing
sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted
version: "1.3.2"
uuid:
dependency: transitive
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.dev"
source: hosted
version: "4.5.1"
vector_math:
dependency: transitive
description:
@ -200,6 +741,46 @@ packages:
url: "https://pub.dev"
source: hosted
version: "14.2.5"
watcher:
dependency: transitive
description:
name: watcher
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
web:
dependency: transitive
description:
name: web
sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
url: "https://pub.dev"
source: hosted
version: "1.1.0"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
url: "https://pub.dev"
source: hosted
version: "0.1.6"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
yaml:
dependency: transitive
description:
name: yaml
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.5.3 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"

View File

@ -1,4 +1,4 @@
name: src
name: flutter_jwt_auth
description: "A new Flutter project."
publish_to: 'none'
version: 0.1.0
@ -7,14 +7,29 @@ environment:
sdk: ^3.5.3
dependencies:
dio: ^5.7.0
flutter:
sdk: flutter
flutter_riverpod: ^2.5.1
flutter_secure_storage: ^4.2.1
freezed_annotation: ^2.4.4
json_annotation: ^4.9.0
pretty_dio_logger: ^1.4.0
retrofit: ^4.4.1
riverpod_annotation: ^2.3.5
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^4.0.0
riverpod_generator: ^2.4.3
build_runner: ^2.4.12
custom_lint: ^0.6.7
riverpod_lint: ^2.3.13
freezed: ^2.5.7
json_serializable: ^6.8.0
retrofit_generator: ^9.1.2
flutter:
uses-material-design: true