Home Widget | Flutter
- Home Widget | Flutter
Home Widget Android integrado com Flutter (Agenda Fácil)
1️⃣ CONCEITO FUNDAMENTAL (ANTES DE CODAR)
❗ O que é um Home Widget (Android)
Um Home Widget:
- vive fora do app
- roda sem Flutter
- NÃO usa Dart
- NÃO usa Bloc
- NÃO usa Widgets Flutter
Ele é:
- XML (layout)
- Kotlin/Java (lógica)
- atualizado por dados persistidos (SharedPreferences)
🧠 Papel de cada camada
Flutter App (UI, Bloc, SQLite)
↓
HomeWidget.saveWidgetData (Flutter)
↓
SharedPreferences (Android)
↓
AgendaWidget.kt (Kotlin)
↓
agenda_widget.xml (layout)
↓
Tela inicial do Android
⚠️ Regra de ouro
❌ Você NUNCA coloca uma tela Flutter no widget ✅ Você cria um RESUMO NATIVO e abre o app ao clicar
2️⃣ PRÉ-REQUISITOS
- Flutter instalado
- Android Studio
- App Flutter rodando no Android
- Package definido (ex:
com.agendafacil.app)
3️⃣ ADICIONAR DEPENDÊNCIA FLUTTER
📄 pubspec.yaml
dependencies:
home_widget: ^0.6.0
Depois:
flutter pub get
👉 Essa dependência só é usada no Flutter, não no Kotlin.
4️⃣ ARQUITETURA ADOTADA (CORRETA)
Onde cada responsabilidade fica
| Camada | Responsabilidade |
|---|---|
| SQLite | Dados persistentes |
| Repository | Acesso ao banco |
| Cubit | Estado + efeitos colaterais |
| Service Flutter | Exportar dados para widget |
| SharedPreferences | Ponte Flutter ↔ Android |
| Kotlin Widget | Renderizar widget |
| XML Widget | Layout visual |
5️⃣ CRIANDO O SERVICE DO WIDGET (FLUTTER)
📁 Caminho
lib/services/home_widget_service.dart
📄 Código
import 'package:home_widget/home_widget.dart';
import 'package:intl/intl.dart';
import '../dominio/models/agenda_model.dart';
class HomeWidgetService {
static Future<void> atualizarWidgetAgenda({
required DateTime data,
required List<AgendaItemModel> eventos,
required int totalHorarios,
}) async {
final hoje = DateFormat('dd/MM/yyyy').format(data);
final eventosHoje = eventos.where((e) =>
e.data.year == data.year &&
e.data.month == data.month &&
e.data.day == data.day).toList();
eventosHoje.sort((a, b) => a.data.compareTo(b.data));
final proximo = eventosHoje.isNotEmpty ? eventosHoje.first : null;
await HomeWidget.saveWidgetData('data', hoje);
await HomeWidget.saveWidgetData(
'proximo',
proximo != null
? '${DateFormat('HH:mm').format(proximo.data)} - ${proximo.nome}'
: 'Nenhum agendamento',
);
await HomeWidget.saveWidgetData('ocupados', eventosHoje.length);
await HomeWidget.saveWidgetData(
'livres',
totalHorarios - eventosHoje.length,
);
await HomeWidget.updateWidget(name: 'AgendaWidget');
}
}
6️⃣ ONDE CHAMAR ISSO (Cubit – LOCAL CORRETO)
📁 lib/ui/states/agenda/agenda_cubit.dart
❗ Regra importante
👉 Atualize o widget APENAS quando o estado final estiver pronto
Implementação correta:
Future<void> obterAgendas() async {
emit(AgendaCarregando());
try {
final agendas = await AgendaRepository.obterAgendasAsync();
emit(AgendaCarregado(agendas));
await HomeWidgetService.atualizarWidgetAgenda(
data: DateTime.now(),
eventos: agendas,
totalHorarios: 24, // ou sua regra real
);
} catch (_) {
emit(AgendaErro("Erro ao carregar agendas"));
}
}
👉 Todos os outros métodos chamam obterAgendas(), então não duplica lógica.
7️⃣ CRIANDO O WIDGET ANDROID (NATIVO)
📁 Estrutura correta
android/app/src/main/
├── kotlin/com/agendafacil/app/AgendaWidget.kt
├── res/layout/agenda_widget.xml
├── res/xml/agenda_widget_info.xml
AgendaWidget.kt→ É a classe nativa Android que representa o widget. Ela estende AppWidgetProvider, que é exigido pelo sistema Android.agenda_widget.xml→ É o layout visual do widget, feito em XML nativo do Android. Define o que aparece no widget como: Textos, ícones, botões, cores, alinhamentoagenda_widget_info.xml→ Arquivo de configuração do widget para o sistema Android. Define qual layout usar, tamanho mínimo do layout, com que frequência ele atualiza
- Fluxo
Flutter app
↓ (MethodChannel / SharedPreferences)
AgendaWidget.kt
↓
agenda_widget.xml
- Resumo
| Arquivo | Função |
|---|---|
AgendaWidget.kt | Lógica do widget (Android nativo) |
agenda_widget.xml | Layout visual do widget |
agenda_widget_info.xml | Configuração para o sistema |
8️⃣ LAYOUT DO WIDGET (XML)
📁 res/layout/agenda_widget.xml
❗ REGRAS DE WIDGET
- TODA View precisa de
layout_widthelayout_height - Só views simples
- Nada de Material, Constraint, RecyclerView
Layout seguro:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="12dp"
android:background="@android:color/white">
<TextView
android:id="@+id/txtData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="14sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/txtProximo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:textSize="13sp"/>
<TextView
android:id="@+id/txtResumo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:textSize="12sp"/>
</LinearLayout>
9️⃣ CONFIGURAÇÃO DO WIDGET
📁 res/xml/agenda_widget_info.xml
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="250dp"
android:minHeight="120dp"
android:updatePeriodMillis="0"
android:initialLayout="@layout/agenda_widget"
android:resizeMode="horizontal|vertical"
android:widgetCategory="home_screen"/>
🔟 LÓGICA DO WIDGET (KOTLIN)
📁 AgendaWidget.kt
❗ Importante
- NÃO usar
HomeWidgetPreferences - Ler direto do SharedPreferences
package com.agendafacil.app
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.content.SharedPreferences
import android.widget.RemoteViews
class AgendaWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
val prefs: SharedPreferences =
context.getSharedPreferences("HomeWidgetPreferences", Context.MODE_PRIVATE)
for (id in appWidgetIds) {
val views = RemoteViews(context.packageName, R.layout.agenda_widget)
views.setTextViewText(
R.id.txtData,
prefs.getString("data", "Hoje") ?: "Hoje"
)
views.setTextViewText(
R.id.txtProximo,
"Próximo: ${prefs.getString("proximo", "Nenhum")}"
)
views.setTextViewText(
R.id.txtResumo,
"Ocupados: ${prefs.getInt("ocupados", 0)} | Livres: ${prefs.getInt("livres", 0)}"
)
appWidgetManager.updateAppWidget(id, views)
}
}
}
1️⃣1️⃣ REGISTRO NO MANIFEST
📁 AndroidManifest.xml
Dentro de <application>:
<receiver
android:name=".AgendaWidget"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/agenda_widget_info" />
</receiver>
1️⃣2️⃣ DEBUG (OBRIGATÓRIO SABER)
Onde ver erro real do widget
adb logcat | findstr AppWidget
adb logcat | findstr InflateException
Erro clássico
You must supply a layout_width attribute
➡ sempre erro de XML
1️⃣3️⃣ PERSONALIZAÇÃO DO WIDGET
O que pode
- cores sólidas
- fontes padrão
- ImageView com ícone
- background color
O que NÃO pode
- tema Material
- fontes custom
- animações
- listas dinâmicas
Exemplo de cor custom
android:background="#FFF3E0"
1️⃣4️⃣ CLIQUE NO WIDGET (abrir Agenda)
No Kotlin:
val intent = context.packageManager
.getLaunchIntentForPackage(context.packageName)
views.setOnClickPendingIntent(
R.id.txtData,
PendingIntent.getActivity(
context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
)