Kaique Mitsuo Silva Yamamoto
Arquitetura software

Ecossistema Flutter e Dart

Flutter e um framework de UI da Google para criar aplicacoes nativas para mobile, web e desktop a partir de uma unica base de codigo. Dart e a linguagem de programacao usada pelo Flutter.

Dart - A Linguagem

Caracteristicas

CaracteristicaDescricao
TipagemEstatica e forte com inferencia
Null SafetySeguranca contra null por padrao
AOT/JITCompilacao ahead-of-time e just-in-time
AsyncSuporte nativo a Future e Stream
IsolatesConcorrencia sem compartilhamento de memoria

Sintaxe Basica

// Variaveis e tipos
String nome = 'Kaique';
int idade = 30;
double altura = 1.75;
bool ativo = true;
var automatico = 'inferido'; // String

// Null safety
String? nullable; // Pode ser null
String nonNull = 'sempre tem valor';

// Late initialization
late String inicializadoDepois;

// Funcoes
int soma(int a, int b) => a + b;

// Named parameters
void criarUsuario({required String nome, int? idade}) {
  print('Nome: $nome, Idade: ${idade ?? "N/A"}');
}

// Classes
class Usuario {
  final String nome;
  final String email;
  int? idade;

  Usuario({required this.nome, required this.email, this.idade});

  // Factory constructor
  factory Usuario.fromJson(Map<String, dynamic> json) {
    return Usuario(
      nome: json['nome'],
      email: json['email'],
      idade: json['idade'],
    );
  }
}

// Async/Await
Future<String> fetchData() async {
  final response = await http.get(Uri.parse('https://api.example.com'));
  return response.body;
}

// Streams
Stream<int> contagem() async* {
  for (int i = 1; i <= 5; i++) {
    await Future.delayed(Duration(seconds: 1));
    yield i;
  }
}

Flutter - Fundamentos

Estrutura de Projeto

meu_app/
├── lib/
│   ├── main.dart              # Entry point
│   ├── app.dart               # MaterialApp/CupertinoApp
│   ├── core/
│   │   ├── constants/
│   │   ├── theme/
│   │   └── utils/
│   ├── features/
│   │   ├── auth/
│   │   │   ├── data/
│   │   │   ├── domain/
│   │   │   └── presentation/
│   │   └── home/
│   └── shared/
│       ├── widgets/
│       └── services/
├── test/
├── android/
├── ios/
├── web/
├── pubspec.yaml
└── analysis_options.yaml

Widgets Fundamentais

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Meu App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.purple),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'Bem-vindo!',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            ElevatedButton(
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (_) => const DetalhesPage()),
                );
              },
              child: const Text('Ver Detalhes'),
            ),
          ],
        ),
      ),
    );
  }
}

StatefulWidget

class ContadorPage extends StatefulWidget {
  const ContadorPage({super.key});

  @override
  State<ContadorPage> createState() => _ContadorPageState();
}

class _ContadorPageState extends State<ContadorPage> {
  int _contador = 0;

  void _incrementar() {
    setState(() {
      _contador++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Contador')),
      body: Center(
        child: Text(
          '$_contador',
          style: Theme.of(context).textTheme.headlineLarge,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementar,
        child: const Icon(Icons.add),
      ),
    );
  }
}

Gerenciamento de Estado

Provider

// pubspec.yaml: provider: ^6.0.0

// Model
class CartModel extends ChangeNotifier {
  final List<Item> _items = [];

  List<Item> get items => List.unmodifiable(_items);
  int get totalItems => _items.length;
  double get totalPrice => _items.fold(0, (sum, item) => sum + item.price);

  void add(Item item) {
    _items.add(item);
    notifyListeners();
  }

  void remove(Item item) {
    _items.remove(item);
    notifyListeners();
  }
}

// Setup
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => CartModel(),
      child: const MyApp(),
    ),
  );
}

// Consumir
class CartPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<CartModel>(
      builder: (context, cart, child) {
        return Text('Total: ${cart.totalItems} items');
      },
    );
  }
}

// Ou usando context.watch/read
class CartButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final cart = context.watch<CartModel>();
    return Badge(
      label: Text('${cart.totalItems}'),
      child: IconButton(
        icon: const Icon(Icons.shopping_cart),
        onPressed: () => context.read<CartModel>().add(item),
      ),
    );
  }
}

Riverpod

// pubspec.yaml: flutter_riverpod: ^2.0.0

import 'package:flutter_riverpod/flutter_riverpod.dart';

// Providers
final contadorProvider = StateProvider<int>((ref) => 0);

final usuarioProvider = FutureProvider<Usuario>((ref) async {
  final response = await http.get(Uri.parse('/api/usuario'));
  return Usuario.fromJson(jsonDecode(response.body));
});

final todosProvider = StateNotifierProvider<TodosNotifier, List<Todo>>((ref) {
  return TodosNotifier();
});

class TodosNotifier extends StateNotifier<List<Todo>> {
  TodosNotifier() : super([]);

  void add(Todo todo) {
    state = [...state, todo];
  }

  void remove(String id) {
    state = state.where((todo) => todo.id != id).toList();
  }

  void toggle(String id) {
    state = state.map((todo) {
      if (todo.id == id) {
        return todo.copyWith(completed: !todo.completed);
      }
      return todo;
    }).toList();
  }
}

// Setup
void main() {
  runApp(
    const ProviderScope(child: MyApp()),
  );
}

// Consumir
class ContadorPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final contador = ref.watch(contadorProvider);

    return Scaffold(
      body: Center(child: Text('$contador')),
      floatingActionButton: FloatingActionButton(
        onPressed: () => ref.read(contadorProvider.notifier).state++,
        child: const Icon(Icons.add),
      ),
    );
  }
}

// Async data
class UsuarioPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final usuarioAsync = ref.watch(usuarioProvider);

    return usuarioAsync.when(
      data: (usuario) => Text(usuario.nome),
      loading: () => const CircularProgressIndicator(),
      error: (err, stack) => Text('Erro: $err'),
    );
  }
}

BLoC

// pubspec.yaml: flutter_bloc: ^8.0.0

import 'package:flutter_bloc/flutter_bloc.dart';

// Events
abstract class ContadorEvent {}
class Incrementar extends ContadorEvent {}
class Decrementar extends ContadorEvent {}

// State
class ContadorState {
  final int valor;
  ContadorState(this.valor);
}

// Bloc
class ContadorBloc extends Bloc<ContadorEvent, ContadorState> {
  ContadorBloc() : super(ContadorState(0)) {
    on<Incrementar>((event, emit) {
      emit(ContadorState(state.valor + 1));
    });

    on<Decrementar>((event, emit) {
      emit(ContadorState(state.valor - 1));
    });
  }
}

// Cubit (simplificado)
class ContadorCubit extends Cubit<int> {
  ContadorCubit() : super(0);

  void incrementar() => emit(state + 1);
  void decrementar() => emit(state - 1);
}

// Setup
void main() {
  runApp(
    BlocProvider(
      create: (_) => ContadorCubit(),
      child: const MyApp(),
    ),
  );
}

// Consumir
class ContadorPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: BlocBuilder<ContadorCubit, int>(
          builder: (context, count) => Text('$count'),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<ContadorCubit>().incrementar(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

Go Router

// pubspec.yaml: go_router: ^12.0.0

import 'package:go_router/go_router.dart';

final router = GoRouter(
  initialLocation: '/',
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomePage(),
      routes: [
        GoRoute(
          path: 'detalhes/:id',
          builder: (context, state) {
            final id = state.pathParameters['id']!;
            return DetalhesPage(id: id);
          },
        ),
      ],
    ),
    GoRoute(
      path: '/perfil',
      builder: (context, state) => const PerfilPage(),
    ),
    ShellRoute(
      builder: (context, state, child) => MainLayout(child: child),
      routes: [
        GoRoute(path: '/home', builder: (_, __) => const HomePage()),
        GoRoute(path: '/busca', builder: (_, __) => const BuscaPage()),
        GoRoute(path: '/config', builder: (_, __) => const ConfigPage()),
      ],
    ),
  ],
  redirect: (context, state) {
    final isLoggedIn = authService.isLoggedIn;
    final isLoginRoute = state.matchedLocation == '/login';

    if (!isLoggedIn && !isLoginRoute) return '/login';
    if (isLoggedIn && isLoginRoute) return '/';
    return null;
  },
);

// Uso
void main() {
  runApp(MaterialApp.router(routerConfig: router));
}

// Navegar
context.go('/detalhes/123');
context.push('/perfil');
context.pop();

HTTP e APIs

Dio

// pubspec.yaml: dio: ^5.0.0

import 'package:dio/dio.dart';

class ApiClient {
  late final Dio _dio;

  ApiClient() {
    _dio = Dio(BaseOptions(
      baseUrl: 'https://api.example.com',
      connectTimeout: const Duration(seconds: 10),
      receiveTimeout: const Duration(seconds: 10),
      headers: {'Content-Type': 'application/json'},
    ));

    _dio.interceptors.add(InterceptorsWrapper(
      onRequest: (options, handler) {
        final token = authService.token;
        if (token != null) {
          options.headers['Authorization'] = 'Bearer $token';
        }
        return handler.next(options);
      },
      onError: (error, handler) {
        if (error.response?.statusCode == 401) {
          authService.logout();
        }
        return handler.next(error);
      },
    ));
  }

  Future<List<Usuario>> getUsuarios() async {
    final response = await _dio.get('/usuarios');
    return (response.data as List)
        .map((json) => Usuario.fromJson(json))
        .toList();
  }

  Future<Usuario> createUsuario(Usuario usuario) async {
    final response = await _dio.post('/usuarios', data: usuario.toJson());
    return Usuario.fromJson(response.data);
  }
}

Retrofit

// pubspec.yaml:
//   retrofit: ^4.0.0
//   retrofit_generator: ^8.0.0 (dev)
//   build_runner: ^2.0.0 (dev)

import 'package:retrofit/retrofit.dart';
import 'package:dio/dio.dart';

part 'api_service.g.dart';

@RestApi(baseUrl: 'https://api.example.com')
abstract class ApiService {
  factory ApiService(Dio dio, {String baseUrl}) = _ApiService;

  @GET('/usuarios')
  Future<List<Usuario>> getUsuarios();

  @GET('/usuarios/{id}')
  Future<Usuario> getUsuario(@Path('id') String id);

  @POST('/usuarios')
  Future<Usuario> createUsuario(@Body() Usuario usuario);

  @PUT('/usuarios/{id}')
  Future<Usuario> updateUsuario(@Path('id') String id, @Body() Usuario usuario);

  @DELETE('/usuarios/{id}')
  Future<void> deleteUsuario(@Path('id') String id);
}

Armazenamento Local

Hive

// pubspec.yaml:
//   hive: ^2.0.0
//   hive_flutter: ^1.0.0
//   hive_generator: ^2.0.0 (dev)

import 'package:hive_flutter/hive_flutter.dart';

part 'usuario.g.dart';

@HiveType(typeId: 0)
class Usuario extends HiveObject {
  @HiveField(0)
  String nome;

  @HiveField(1)
  String email;

  Usuario({required this.nome, required this.email});
}

// Inicializar
Future<void> main() async {
  await Hive.initFlutter();
  Hive.registerAdapter(UsuarioAdapter());
  await Hive.openBox<Usuario>('usuarios');
  runApp(const MyApp());
}

// Usar
class UsuarioRepository {
  final Box<Usuario> _box = Hive.box<Usuario>('usuarios');

  List<Usuario> getAll() => _box.values.toList();

  Future<void> add(Usuario usuario) => _box.add(usuario);

  Future<void> update(int index, Usuario usuario) => _box.putAt(index, usuario);

  Future<void> delete(int index) => _box.deleteAt(index);
}

SharedPreferences

// pubspec.yaml: shared_preferences: ^2.0.0

import 'package:shared_preferences/shared_preferences.dart';

class PreferencesService {
  late SharedPreferences _prefs;

  Future<void> init() async {
    _prefs = await SharedPreferences.getInstance();
  }

  String? get token => _prefs.getString('token');
  set token(String? value) {
    if (value != null) {
      _prefs.setString('token', value);
    } else {
      _prefs.remove('token');
    }
  }

  bool get isDarkMode => _prefs.getBool('darkMode') ?? false;
  set isDarkMode(bool value) => _prefs.setBool('darkMode', value);

  String get locale => _prefs.getString('locale') ?? 'pt_BR';
  set locale(String value) => _prefs.setString('locale', value);
}

Testes

Unit Tests

// test/usuario_test.dart
import 'package:flutter_test/flutter_test.dart';

void main() {
  group('Usuario', () {
    test('deve criar usuario a partir de JSON', () {
      final json = {'nome': 'Kaique', 'email': 'kaique@email.com'};
      final usuario = Usuario.fromJson(json);

      expect(usuario.nome, 'Kaique');
      expect(usuario.email, 'kaique@email.com');
    });

    test('deve converter usuario para JSON', () {
      final usuario = Usuario(nome: 'Kaique', email: 'kaique@email.com');
      final json = usuario.toJson();

      expect(json['nome'], 'Kaique');
      expect(json['email'], 'kaique@email.com');
    });
  });
}

Widget Tests

// test/contador_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('Contador deve incrementar', (tester) async {
    await tester.pumpWidget(const MaterialApp(home: ContadorPage()));

    expect(find.text('0'), findsOneWidget);

    await tester.tap(find.byIcon(Icons.add));
    await tester.pump();

    expect(find.text('1'), findsOneWidget);
  });

  testWidgets('Deve exibir lista de usuarios', (tester) async {
    final usuarios = [
      Usuario(nome: 'User 1', email: 'user1@email.com'),
      Usuario(nome: 'User 2', email: 'user2@email.com'),
    ];

    await tester.pumpWidget(
      MaterialApp(home: UsuariosPage(usuarios: usuarios)),
    );

    expect(find.text('User 1'), findsOneWidget);
    expect(find.text('User 2'), findsOneWidget);
  });
}

Integration Tests

// integration_test/app_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  testWidgets('Fluxo completo de login', (tester) async {
    await tester.pumpWidget(const MyApp());
    await tester.pumpAndSettle();

    // Digitar credenciais
    await tester.enterText(find.byKey(const Key('email')), 'test@email.com');
    await tester.enterText(find.byKey(const Key('senha')), '123456');

    // Fazer login
    await tester.tap(find.byKey(const Key('btnLogin')));
    await tester.pumpAndSettle();

    // Verificar navegacao para home
    expect(find.text('Bem-vindo'), findsOneWidget);
  });
}

Pacotes Essenciais

CategoriaPacoteDescricao
Estadoriverpod, bloc, providerGerenciamento de estado
Navegacaogo_router, auto_routeRoteamento declarativo
HTTPdio, retrofitCliente HTTP
Storagehive, shared_preferencesArmazenamento local
Injecaoget_it, injectableDependency injection
Freezedfreezed, json_serializableCode generation
UIflutter_hooks, animationsUtilitarios de UI
Formsreactive_forms, flutter_form_builderFormularios
Imagenscached_network_imageCache de imagens
Notificacoesfirebase_messaging, flutter_local_notificationsPush notifications

Recursos