Partilhar: WhatsApp
aulify · Sebenta
UC · Unidade de Competência · UC02835

Desenvolvimento de aplicações móveis

Sebenta · Flutter, UI móvel, estado, APIs
50h · 4.5 pontos crédito Curso: T. Desenv. Software ↗ Referencial oficial SNQ
Índice

Apresentação

UC02835 (50h · 4,5 pts) ensina a construir aplicações móveis. Aborda as abordagens (nativo, cross-platform, PWA), foca Flutter (Dart), e cobre UI, navegação, estado, consumo de APIs, armazenamento local, UX móvel e publicação nas lojas.

Mobile não é "web mais pequena": toque, gestos, offline, bateria, ecrãs muito variados, permissões e lojas de apps mudam tudo.

Abordagens

Nativo

Prós: performance máxima, acesso total ao hardware e APIs do SO, melhor integração. Contras: duas bases de código, duas equipas/competências, mais custo.

Cross-platform

Uma base de código → várias plataformas.

Prós: 1 código para Android + iOS (+ web/desktop), time-to-market, equipa pequena. Contras: algum overhead, casos extremos podem precisar de código nativo.

PWA (Progressive Web App)

App web que instala a partir do browser, funciona offline (service worker), recebe notificações.

Prós: sem loja, 1 código (web), actualização instantânea. Contras: acesso limitado a hardware, menos integração, iOS restringe.

Como escolher

Caso Recomendação
Conteúdo simples, orçamento mínimo PWA
App de negócio, equipa pequena Flutter / React Native
Performance crítica, jogos, hardware intensivo Nativo

Flutter · fundamentos

Setup

# Instalar Flutter SDK (flutter.dev)
flutter doctor          # verifica ambiente
flutter create minha_app
cd minha_app
flutter run             # corre em emulador/dispositivo

Editor: VSCode ou Android Studio com plugin Flutter/Dart.

Dart em 1 minuto

void main() {
  String nome = 'Ana';
  int idade = 17;
  var lista = [1, 2, 3];           // tipo inferido
  final pi = 3.14;                 // imutável

  if (idade >= 18) {
    print('$nome é maior');
  }

  for (var n in lista) print(n);

  int soma(int a, int b) => a + b;
  print(soma(2, 3));
}

Dart: tipado, OO, null safety (String? aceita null, String não).

Tudo é widget

A UI Flutter é uma árvore de widgets (componentes). Layout = compor widgets.

import 'package:flutter/material.dart';

void main() => runApp(const MinhaApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Aulify',
      theme: ThemeData(primarySwatch: Colors.indigo),
      home: Scaffold(
        appBar: AppBar(title: const Text('Aulify')),
        body: const Center(child: Text('Olá, mundo!')),
        floatingActionButton: FloatingActionButton(
          onPressed: () {},
          child: const Icon(Icons.add),
        ),
      ),
    );
  }
}

Scaffold fornece a estrutura padrão de um ecrã Material.

Widgets essenciais

Conteúdo: - Text('...'), Icon(Icons.x), Image.network/asset(...).

Layout: - Column (vertical), Row (horizontal). - Stack (sobrepostos), Expanded, Flexible. - Container (caixa com padding, margin, decoração). - Padding, Center, SizedBox. - ListView (lista com scroll), GridView.

Interacção: - ElevatedButton, TextButton, IconButton. - TextField, Checkbox, Switch, Slider. - GestureDetector, InkWell.

Column(
  children: [
    Text('Lista de produtos'),
    Expanded(
      child: ListView(
        children: const [
          ListTile(leading: Icon(Icons.shopping_cart), title: Text('Caneta')),
          ListTile(leading: Icon(Icons.book), title: Text('Caderno')),
        ],
      ),
    ),
  ],
)

Stateless vs Stateful

StatelessWidget — não muda depois de construído.

class Saudacao extends StatelessWidget {
  final String nome;
  const Saudacao(this.nome, {super.key});

  @override
  Widget build(BuildContext context) => Text('Olá, $nome');
}

StatefulWidget — tem estado que muda ao longo do tempo.

class Contador extends StatefulWidget {
  const Contador({super.key});
  @override
  State<Contador> createState() => _ContadorState();
}

class _ContadorState extends State<Contador> {
  int _n = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text('Cliques: $_n', style: const TextStyle(fontSize: 32)),
        ElevatedButton(
          onPressed: () => setState(() => _n++),
          child: const Text('Incrementar'),
        ),
      ],
    );
  }
}

setState() notifica o Flutter para reconstruir o widget com o novo estado.

Push / pop

// Avançar para novo ecrã
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => const DetalheScreen()),
);

// Voltar
Navigator.pop(context);

// Passar dados
Navigator.push(context, MaterialPageRoute(
  builder: (c) => DetalheScreen(produtoId: 42),
));

Rotas nomeadas

MaterialApp(
  initialRoute: '/',
  routes: {
    '/': (c) => const HomeScreen(),
    '/perfil': (c) => const PerfilScreen(),
    '/sobre': (c) => const SobreScreen(),
  },
);

Navigator.pushNamed(context, '/perfil');

Para apps maiores: go_router (rotas declarativas, deep links).

Gestão de estado

À medida que a app cresce, setState não chega — estado precisa de ser partilhado entre ecrãs.

Solução Quando
setState Estado local de 1 widget
Provider Estado partilhado, simples
Riverpod Provider melhorado, testável
Bloc Apps grandes, fluxos complexos
GetX Tudo-em-um (controverso)

Provider (exemplo)

class CarrinhoModel extends ChangeNotifier {
  final List<String> _itens = [];
  List<String> get itens => List.unmodifiable(_itens);

  void adicionar(String item) {
    _itens.add(item);
    notifyListeners();   // avisa os widgets que escutam
  }
}

// No topo da app
ChangeNotifierProvider(
  create: (_) => CarrinhoModel(),
  child: const MinhaApp(),
)

// Num widget
final carrinho = context.watch<CarrinhoModel>();
Text('${carrinho.itens.length} itens');

context.read<CarrinhoModel>().adicionar('Caneta');

Princípio: começar simples (setState) e subir de complexidade só quando necessário.

Consumir APIs

import 'package:http/http.dart' as http;
import 'dart:convert';

Future<List<dynamic>> buscarProdutos() async {
  final resposta = await http.get(
    Uri.parse('https://api.aulify.pt/produtos'),
  );
  if (resposta.statusCode == 200) {
    return jsonDecode(resposta.body) as List;
  } else {
    throw Exception('Erro ${resposta.statusCode}');
  }
}

async/await/Future — operações que demoram (rede, disco) não bloqueiam a UI.

FutureBuilder

Mostra loading, depois dados ou erro:

FutureBuilder<List<dynamic>>(
  future: buscarProdutos(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return const Center(child: CircularProgressIndicator());
    }
    if (snapshot.hasError) {
      return Center(child: Text('Erro: ${snapshot.error}'));
    }
    final produtos = snapshot.data!;
    return ListView.builder(
      itemCount: produtos.length,
      itemBuilder: (c, i) => ListTile(
        title: Text(produtos[i]['nome']),
      ),
    );
  },
)

Modelos de dados

class Produto {
  final int id;
  final String nome;
  final double preco;

  Produto({required this.id, required this.nome, required this.preco});

  factory Produto.fromJson(Map<String, dynamic> json) => Produto(
    id: json['id'],
    nome: json['nome'],
    preco: (json['preco'] as num).toDouble(),
  );
}

Converter JSON em objectos torna o código mais seguro e legível.

Armazenamento local

Pacote Uso
shared_preferences Chave-valor simples (settings, flags)
flutter_secure_storage Dados sensíveis (token), encriptado
sqflite SQLite no dispositivo
hive / isar NoSQL local rápido
path_provider Aceder a pastas do dispositivo
final prefs = await SharedPreferences.getInstance();
await prefs.setString('token', 'abc123');
await prefs.setBool('dark_mode', true);

final token = prefs.getString('token');
final dark = prefs.getBool('dark_mode') ?? false;

Token de autenticação → flutter_secure_storage (encriptado), não shared_preferences.

UX móvel

Princípios

Acessibilidade

Performance

Publicação

Preparação

Builds

flutter build apk --release          # Android APK
flutter build appbundle --release    # Android (Play Store, .aab)
flutter build ios --release          # iOS (precisa de macOS + Xcode)

Lojas

Loja Conta Custo Revisão
Google Play Google Play Console 25 $ (uma vez) horas a dias
App Store Apple Developer 99 $/ano 1-3 dias

Apps são revistas: cumprir guidelines (privacidade, conteúdo, permissões justificadas) ou são rejeitadas.

Apêndices

A · Cheatsheet Flutter

StatelessWidget / StatefulWidget
setState(() => x = y);
Navigator.push / pop / pushNamed
Column / Row / Stack / Expanded
ListView.builder(itemCount:, itemBuilder:)
FutureBuilder(future:, builder:)
context.watch<T>() / context.read<T>()
flutter run / build apk / build appbundle

B · Glossário

Dart. Linguagem do Flutter. Widget. Componente de UI Flutter. State. Dados que mudam e redesenham a UI. Hot reload. Ver mudanças sem reiniciar a app. PWA. Progressive Web App. Scaffold. Estrutura base de ecrã Material.

C · Recursos