Reputation: 11
I'm implementing a centralized navigation manager (KNavigator
) to handle typed navigation with generics.
However, when passing functions that accept a generic type (e.g., displayItem: (T) => String
), Dart treats them as dynamic, leading to type errors.
I get the following error:
Unhandled Exception: type '(Accounts) => String' is not a subtype of type '(dynamic) => String'
Expected Behavior
Here is my KNavigator
:
part of 'route.dart';
/// [KNavigator] a class where handle navigation cross the App
class KNavigator {
const KNavigator._();
static final GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
static final KNavigatorObserver _navigationObserver = KNavigatorObserver();
/// The name of the route that loads on app startup
static const String initialRoute = KRoutes.splashScreen;
/// [observers] getter method
static List<NavigatorObserver> get observers => [_navigationObserver];
static MaterialPageRoute<dynamic> generateRoute<T>(RouteSettings settings) {
switch (settings.name) {
case KRoutes.selectionPage:
final args = settings.arguments! as SelectionPageArguments<T>;
return _setPage<T>(
page: SelectionPage<T>(
title: args.title,
items: args.items,
displayItem: args.displayItem,
displaySubtitle: args.displaySubtitle,
leading: args.leading,
trailing: args.trailing,
),
settings: settings,
);
default:
return _errorRoute();
}
}
/// [_errorRoute] in case no route found
static MaterialPageRoute<T> _errorRoute<T>() {
return MaterialPageRoute<T>(
builder: (_) => Scaffold(
appBar: AppBar(
title: const Text('Unknown Route'),
),
body: const Center(
child: Text('Unknown Route'),
),
),
);
}
/// [_setPage] which set the new screen into the material app
static MaterialPageRoute<T> _setPage<T>({
required Widget page,
required RouteSettings settings,
}) {
return MaterialPageRoute<T>(
builder: (_) => page,
settings: settings,
);
}
/// [pushNamed] push to a new route
static Future<T?> pushNamed<T extends Object?>(
String routeName, {
dynamic args,
}) {
return navigatorKey.currentState!.pushNamed<T>(routeName, arguments: args);
}
}
This is how I'm navigating to SelectionPage
/// [onTapSelectAccount] to navigate to select page with account list in
/// there
static Future<Accounts?> onTapSelectAccount(
BuildContext context,
String account,
List<Accounts> accountsList,
) async {
final selectedAccount = await KNavigator.pushNamed<Accounts?>(
KRoutes.selectionPage,
args: SelectionPageArguments<Accounts>(
title: account,
items: accountsList,
displayItem: (account) => account.accNickName.isEmpty
? account.accountName!
: account.accNickName,
displaySubtitle: (account) => Text(account.accountID!),
trailing: (account) => Text(
'${account.currencyCode!} ${FormatUtil.formatBalance(
account.balance!,
currencyCode: account.currencyCode!,
)}',
),
),
);
return selectedAccount;
}
}
This is my generic argument class
class SelectionPageArguments<T> {
const SelectionPageArguments({
required this.title,
required this.items,
required this.displayItem,
this.displaySubtitle,
this.leading,
this.trailing,
});
/// Title for the selection screen
final String title;
/// List of selectable items
final List<T> items;
/// Function to format how each item should be displayed
final String Function(T) displayItem;
/// Function to format how each subtitle should be displayed
final Widget? Function(T)? displaySubtitle;
/// Optional function to provide a leading widget for each item
final Widget? Function(T)? leading;
/// Optional function to provide a trailing widget for each item
final Widget? Function(T)? trailing;
}
and finally this is my SelectionPage
/// [SelectionPage] this is a custom Screen widget that represent the Select
/// screen
class SelectionPage<T> extends StatelessWidget {
/// Creates a [SelectionPage]
const SelectionPage({
required this.title,
required this.items,
required this.displayItem,
this.displaySubtitle,
this.leading,
this.trailing,
super.key,
});
/// Title for the selection screen
final String title;
/// List of selectable items
final List<T> items;
/// Function to format how each item should be displayed
final String Function(T) displayItem;
/// Function to format how each subtitle should be displayed
final Widget? Function(T)? displaySubtitle;
/// Optional function to provide a leading widget for each item
final Widget? Function(T)? leading;
/// Optional function to provide a trailing widget for each item
final Widget? Function(T)? trailing;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBarLinearGradient(
title: title,
),
body: Padding(
padding: const EdgeInsets.all(16),
child: ListView.separated(
itemCount: items.length,
separatorBuilder: (context, index) => const Divider(),
itemBuilder: (context, index) {
final item = items[index];
return ListTile(
onTap: () => Navigator.of(context).pop(item),
title: Text(displayItem(item)),
subtitle: displaySubtitle?.call(item),
leading: leading?.call(item),
trailing: trailing?.call(item),
);
},
),
),
);
}
}
My Question
If I explicitly replace with , everything works fine:
case KRoutes.selectionPage:
final args = settings.arguments! as SelectionPageArguments<Accounts>;
return _setPage<Accounts>(
page: SelectionPage<Accounts>(
title: args.title,
items: args.items,
displayItem: args.displayItem,
displaySubtitle: args.displaySubtitle,
leading: args.leading,
trailing: args.trailing,
),
settings: settings,
);
Upvotes: 0
Views: 25