# Views
- [Introduction](#introduction)
- [Creating Views](#creating-views)
- [Stateless Views](#stateless-views)
- [Stateful Views](#stateful-views)
- [Form Handling](#form-handling)
- [Rendering Async State](#rendering-async-state)
- [Responsive Views](#responsive-views)
- [Generating Views](#generating-views)
## Introduction
Magic Views provide a structured way to separate your UI from your business logic, following the MVC pattern familiar to Laravel developers. Instead of standard Flutter widgets, Magic views extend `MagicView` or `MagicStatefulView`.
### Why Use MagicView?
- **Auto-Injection**: The controller is automatically available via `controller` property
- **Type Safety**: The controller is fully typed (`MagicView`)
- **Consistency**: Matches Laravel's MVC structure
- **Wind UI Integration**: Build UIs with utility-first classes
## Creating Views
To generate a new view, use the Magic CLI:
```bash
magic make:view Dashboard
```
### Stateless Views
Most of your pages will be stateless, relying on the controller for state management:
```dart
import 'package:flutter/material.dart';
import 'package:fluttersdk_magic/fluttersdk_magic.dart';
class DashboardView extends MagicView {
const DashboardView({super.key});
@override
Widget build(BuildContext context) {
return WDiv(
className: 'p-6 flex flex-col gap-4',
children: [
WText('Dashboard', className: 'text-2xl font-bold text-white'),
WText('Welcome back!', className: 'text-gray-400'),
],
);
}
}
```
### Stateful Views
Use `MagicStatefulView` when you need local widget state or form handling:
```dart
class LoginView extends MagicStatefulView {
const LoginView({super.key});
@override
State createState() => _LoginViewState();
}
class _LoginViewState extends MagicStatefulViewState {
late final form = MagicFormData({
'email': '',
'password': '',
'remember_me': false,
}, controller: controller);
@override
void onClose() => form.dispose();
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: controller,
builder: (context, _) => _buildForm(),
);
}
Widget _buildForm() {
return MagicForm(
formData: form,
child: WDiv(
className: 'max-w-md p-6 bg-slate-900 rounded-xl',
children: [
WFormInput(
controller: form['email'],
label: trans('attributes.email'),
placeholder: trans('fields.email_placeholder'),
type: InputType.email,
validator: rules([Required(), Email()], field: 'email'),
),
WFormInput(
controller: form['password'],
label: trans('attributes.password'),
type: InputType.password,
validator: rules([Required(), Min(8)], field: 'password'),
),
WButton(
isLoading: controller.isLoading,
onTap: _submit,
className: 'w-full bg-primary p-4 rounded-lg',
child: WText(trans('auth.login'), className: 'text-white text-center'),
),
],
),
);
}
void _submit() {
final data = form.validated();
if (data.isNotEmpty) {
controller.login(data);
}
}
}
```
## Form Handling
Views use `MagicFormData` for centralized form management:
```dart
late final form = MagicFormData({
'name': '',
'email': '',
'age': 0,
'active': true,
}, controller: controller);
// Access values
form.get('name'); // String value
form.value('active'); // Typed value
form['email']; // TextEditingController
// Set values
form.setValue('name', 'John');
// Validation
final data = form.validated(); // Returns {} if invalid
form.validate(); // Returns bool
// Cleanup
@override
void onClose() => form.dispose();
```
### Validation Rules
Use the `rules()` helper within views for client-side validation:
```dart
WFormInput(
controller: form['email'],
validator: rules([Required(), Email()], field: 'email'),
);
WFormInput(
controller: form['password_confirmation'],
validator: rules([
Required(),
Same('password', valueGetter: () => form['password'].text),
], field: 'password_confirmation'),
);
```
## Rendering Async State
Use `controller.renderState()` to elegantly handle loading, error, and success states:
```dart
@override
Widget build(BuildContext context) {
return controller.renderState(
(users) => UserList(users: users),
onLoading: Center(child: CircularProgressIndicator()),
onError: (msg) => ErrorWidget(message: msg),
onEmpty: EmptyState(message: trans('users.empty')),
);
}
```
Each callback is optional. Magic provides sensible defaults if omitted.
## Responsive Views
For layouts that adapt to screen size, use `LayoutBuilder` with Wind's responsive helpers:
```dart
class DashboardView extends MagicView {
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final isDesktop = wScreenIs(context, 'lg');
if (isDesktop) {
return _buildDesktopLayout();
}
return _buildMobileLayout();
},
);
}
}
```
Or use Wind's responsive prefixes:
```dart
WDiv(
className: '''
flex flex-col gap-4
md:flex-row md:gap-6
lg:gap-8
''',
children: [...],
)
```
## Generating Views
The Magic CLI can generate different types of views:
```bash
# Basic stateless view
magic make:view Dashboard
# Stateful view with form support
magic make:view Auth/Login --stateful
# Nested in subfolder
magic make:view Admin/Users/Index
```
### Command Options
| Option | Description |
|--------|-------------|
| `--stateful` | Create stateful view with `MagicFormData` support |
**Output:** Creates `lib/resources/views/_view.dart`