skills/flutter-ui-ux/assets/app-template.md

797 lines
18 KiB
Markdown

# Flutter App Template
## Basic App Structure
Complete starter template for Flutter applications.
```dart
// main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'app/theme/app_theme.dart';
import 'app/screens/home_screen.dart';
import 'app/screens/detail_screen.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter App',
debugShowCheckedModeBanner: false,
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: ThemeMode.system,
home: HomeScreen(),
routes: {
'/detail': (context) => DetailScreen(),
},
);
}
}
```
## Responsive App Template
Mobile-first responsive design.
```dart
// main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'app/theme/responsive_theme.dart';
import 'app/screens/home_screen.dart';
import 'app/widgets/responsive_layout.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Responsive Flutter App',
debugShowCheckedModeBanner: false,
theme: ResponsiveTheme.lightTheme,
darkTheme: ResponsiveTheme.darkTheme,
themeMode: ThemeMode.system,
home: ResponsiveLayout(
mobile: HomeScreen(),
tablet: HomeScreen(),
desktop: HomeScreen(),
),
);
}
}
// app/widgets/responsive_layout.dart
import 'package:flutter/material.dart';
class ResponsiveLayout extends StatelessWidget {
final Widget mobile;
final Widget? tablet;
final Widget? desktop;
const ResponsiveLayout({
required this.mobile,
this.tablet,
this.desktop,
});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth >= 1200 && desktop != null) {
return desktop!;
} else if (constraints.maxWidth >= 800 && tablet != null) {
return tablet!;
} else {
return mobile;
}
},
);
}
}
```
## Navigation Template
Structured navigation with routes.
```dart
// app/routes/app_routes.dart
class AppRoutes {
static const String home = '/';
static const String detail = '/detail';
static const String settings = '/settings';
static const String profile = '/profile';
}
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case AppRoutes.home:
return MaterialPageRoute(
builder: (_) => HomeScreen(),
settings: settings,
);
case AppRoutes.detail:
final String id = settings.arguments as String;
return MaterialPageRoute(
builder: (_) => DetailScreen(id: id),
settings: settings,
);
case AppRoutes.settings:
return MaterialPageRoute(
builder: (_) => SettingsScreen(),
settings: settings,
);
default:
return MaterialPageRoute(
builder: (_) => NotFoundScreen(),
);
}
}
}
// app/navigation/navigation_service.dart
import 'package:flutter/material.dart';
class NavigationService {
static final GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
static Future<dynamic> navigateTo(String routeName, {Object? arguments}) {
return navigatorKey.currentState!.pushNamed(routeName, arguments: arguments);
}
static void goBack() {
return navigatorKey.currentState!.pop();
}
static Future<dynamic> replaceWith(String routeName, {Object? arguments}) {
return navigatorKey.currentState!.pushReplacementNamed(
routeName,
arguments: arguments,
);
}
static Future<dynamic> navigateToAndClearStack(String routeName, {Object? arguments}) {
return navigatorKey.currentState!.pushNamedAndRemoveUntil(
routeName,
(route) => false,
arguments: arguments,
);
}
}
```
## State Management Template
Provider-based state management.
```dart
// app/providers/app_state_provider.dart
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class AppState extends ChangeNotifier {
bool _isLoading = false;
String? _errorMessage;
bool get isLoading => _isLoading;
String? get errorMessage => _errorMessage;
void setLoading(bool loading) {
_isLoading = loading;
notifyListeners();
}
void setError(String? error) {
_errorMessage = error;
notifyListeners();
}
void clearError() {
_errorMessage = null;
notifyListeners();
}
}
// app/providers/user_provider.dart
import 'package:flutter/foundation.dart';
class UserProvider extends ChangeNotifier {
User? _user;
bool _isLoggedIn = false;
User? get user => _user;
bool get isLoggedIn => _isLoggedIn;
Future<void> login(String email, String password) async {
try {
// Login logic here
_user = User(id: '1', email: email);
_isLoggedIn = true;
notifyListeners();
} catch (e) {
// Handle error
}
}
void logout() {
_user = null;
_isLoggedIn = false;
notifyListeners();
}
}
class User {
final String id;
final String email;
User({required this.id, required this.email});
}
// app/widgets/stateful_builder.dart
import 'package:flutter/material.dart';
class StatefulBuilder<T> extends StatefulWidget {
final T initialValue;
final Widget Function(
T value,
void Function(T) updateValue,
) builder;
const StatefulBuilder({
required this.initialValue,
required this.builder,
});
@override
_StatefulBuilderState<T> createState() => _StatefulBuilderState<T>();
}
class _StatefulBuilderState<T> extends State<StatefulBuilder<T>> {
late T _value;
@override
void initState() {
super.initState();
_value = widget.initialValue;
}
void _updateValue(T newValue) {
setState(() {
_value = newValue;
});
}
@override
Widget build(BuildContext context) {
return widget.builder(_value, _updateValue);
}
}
```
## API Service Template
Structured API communication.
```dart
// app/services/api_service.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
class ApiService {
static const String baseUrl = 'https://api.example.com';
static const Duration timeout = Duration(seconds: 30);
static Map<String, String> get headers => {
'Content-Type': 'application/json',
'Accept': 'application/json',
};
static Future<Map<String, dynamic>> get(String endpoint) async {
try {
final response = await http.get(
Uri.parse('$baseUrl$endpoint'),
headers: headers,
).timeout(timeout);
return _handleResponse(response);
} catch (e) {
throw _handleError(e);
}
}
static Future<Map<String, dynamic>> post(
String endpoint,
Map<String, dynamic> data,
) async {
try {
final response = await http.post(
Uri.parse('$baseUrl$endpoint'),
headers: headers,
body: json.encode(data),
).timeout(timeout);
return _handleResponse(response);
} catch (e) {
throw _handleError(e);
}
}
static Future<Map<String, dynamic>> put(
String endpoint,
Map<String, dynamic> data,
) async {
try {
final response = await http.put(
Uri.parse('$baseUrl$endpoint'),
headers: headers,
body: json.encode(data),
).timeout(timeout);
return _handleResponse(response);
} catch (e) {
throw _handleError(e);
}
}
static Future<void> delete(String endpoint) async {
try {
final response = await http.delete(
Uri.parse('$baseUrl$endpoint'),
headers: headers,
).timeout(timeout);
_handleResponse(response);
} catch (e) {
throw _handleError(e);
}
}
static Map<String, dynamic> _handleResponse(http.Response response) {
if (response.statusCode >= 200 && response.statusCode < 300) {
return json.decode(response.body);
} else {
throw ApiException(
'API Error: ${response.statusCode}',
response.statusCode,
);
}
}
static Exception _handleError(dynamic error) {
if (error is http.TimeoutException) {
return ApiException('Request timeout', 408);
} else if (error is http.ClientException) {
return ApiException('Network error', 0);
} else {
return ApiException('Unknown error', 0);
}
}
}
class ApiException implements Exception {
final String message;
final int statusCode;
ApiException(this.message, this.statusCode);
@override
String toString() => 'ApiException: $message ($statusCode)';
}
```
## Widget Templates
Common reusable widgets.
```dart
// app/widgets/custom_button.dart
import 'package:flutter/material.dart';
class CustomButton extends StatelessWidget {
final String text;
final VoidCallback onPressed;
final bool isLoading;
final Color? color;
final Color? textColor;
final double? width;
final double? height;
const CustomButton({
required this.text,
required this.onPressed,
this.isLoading = false,
this.color,
this.textColor,
this.width,
this.height,
});
@override
Widget build(BuildContext context) {
return SizedBox(
width: width,
height: height ?? 48,
child: ElevatedButton(
onPressed: isLoading ? null : onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: color ?? Theme.of(context).primaryColor,
foregroundColor: textColor ?? Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: isLoading
? SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
textColor ?? Colors.white,
),
),
)
: Text(text),
),
);
}
}
// app/widgets/custom_text_field.dart
import 'package:flutter/material.dart';
class CustomTextField extends StatelessWidget {
final String? label;
final String? hint;
final String? errorText;
final TextEditingController? controller;
final bool obscureText;
final TextInputType keyboardType;
final Function(String)? onChanged;
final Function(String)? onSubmitted;
final Widget? prefixIcon;
final Widget? suffixIcon;
const CustomTextField({
this.label,
this.hint,
this.errorText,
this.controller,
this.obscureText = false,
this.keyboardType = TextInputType.text,
this.onChanged,
this.onSubmitted,
this.prefixIcon,
this.suffixIcon,
});
@override
Widget build(BuildContext context) {
return TextField(
controller: controller,
obscureText: obscureText,
keyboardType: keyboardType,
onChanged: onChanged,
onSubmitted: onSubmitted,
decoration: InputDecoration(
labelText: label,
hintText: hint,
errorText: errorText,
prefixIcon: prefixIcon,
suffixIcon: suffixIcon,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: Theme.of(context).primaryColor,
width: 2,
),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: Theme.of(context).errorColor,
width: 2,
),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
),
);
}
}
// app/widgets/loading_widget.dart
import 'package:flutter/material.dart';
class LoadingWidget extends StatelessWidget {
final String? message;
final double? size;
const LoadingWidget({
this.message,
this.size = 24,
});
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: size,
height: size,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).primaryColor,
),
),
),
if (message != null) ...[
SizedBox(height: 16),
Text(
message!,
style: Theme.of(context).textTheme.bodyMedium,
),
],
],
),
);
}
}
// app/widgets/empty_state_widget.dart
import 'package:flutter/material.dart';
class EmptyStateWidget extends StatelessWidget {
final String title;
final String? subtitle;
final Widget? action;
final IconData? icon;
const EmptyStateWidget({
required this.title,
this.subtitle,
this.action,
this.icon,
});
@override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: EdgeInsets.all(32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (icon != null) ...[
Icon(
icon!,
size: 64,
color: Theme.of(context).disabledColor,
),
SizedBox(height: 16),
],
Text(
title,
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
color: Theme.of(context).disabledColor,
),
textAlign: TextAlign.center,
),
if (subtitle != null) ...[
SizedBox(height: 8),
Text(
subtitle!,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).disabledColor,
),
textAlign: TextAlign.center,
),
],
if (action != null) ...[
SizedBox(height: 24),
action!,
],
],
),
),
);
}
}
```
## Screen Templates
Common screen patterns.
```dart
// app/screens/base_screen.dart
import 'package:flutter/material.dart';
class BaseScreen extends StatelessWidget {
final Widget body;
final String? title;
final List<Widget>? actions;
final Widget? floatingActionButton;
final Widget? bottomNavigationBar;
final bool showAppBar;
const BaseScreen({
required this.body,
this.title,
this.actions,
this.floatingActionButton,
this.bottomNavigationBar,
this.showAppBar = true,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: showAppBar
? AppBar(
title: title != null ? Text(title!) : null,
actions: actions,
)
: null,
body: SafeArea(child: body),
floatingActionButton: floatingActionButton,
bottomNavigationBar: bottomNavigationBar,
);
}
}
// app/screens/list_screen.dart
import 'package:flutter/material.dart';
class ListScreen<T> extends StatelessWidget {
final List<T> items;
final Widget Function(BuildContext, T) itemBuilder;
final String? title;
final Function(T)? onItemTap;
final Widget? emptyState;
final bool isLoading;
const ListScreen({
required this.items,
required this.itemBuilder,
this.title,
this.onItemTap,
this.emptyState,
this.isLoading = false,
});
@override
Widget build(BuildContext context) {
if (isLoading) {
return LoadingWidget();
}
if (items.isEmpty) {
return emptyState ??
EmptyStateWidget(
title: 'No items found',
icon: Icons.inbox_outlined,
);
}
return BaseScreen(
title: title,
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
final item = items[index];
return GestureDetector(
onTap: () => onItemTap?.call(item),
child: itemBuilder(context, item),
);
},
),
);
}
}
```
## Usage Example
Complete app using all templates.
```dart
// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'app/providers/user_provider.dart';
import 'app/services/api_service.dart';
import 'app/widgets/custom_button.dart';
import 'app/screens/base_screen.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (_) => UserProvider(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Template App',
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: ThemeMode.system,
home: HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<UserProvider>(
builder: (context, userProvider, child) {
return BaseScreen(
title: 'Home',
body: Column(
children: [
if (userProvider.isLoggedIn) ...[
Text('Welcome ${userProvider.user?.email}'),
CustomButton(
text: 'Logout',
onPressed: () => userProvider.logout(),
),
] else ...[
Text('Please login'),
CustomButton(
text: 'Login',
onPressed: () => _showLoginDialog(context),
),
],
],
),
);
},
);
}
void _showLoginDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Login'),
content: CustomTextField(
label: 'Email',
hint: 'Enter your email',
),
actions: [
CustomButton(
text: 'Cancel',
onPressed: () => Navigator.pop(context),
),
CustomButton(
text: 'Login',
onPressed: () {
// Login logic
Navigator.pop(context);
},
),
],
),
);
}
}