Skip to main content

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

CamadaResponsabilidade
SQLiteDados persistentes
RepositoryAcesso ao banco
CubitEstado + efeitos colaterais
Service FlutterExportar dados para widget
SharedPreferencesPonte Flutter ↔ Android
Kotlin WidgetRenderizar widget
XML WidgetLayout 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, alinhamento
  • agenda_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
ArquivoFunção
AgendaWidget.ktLógica do widget (Android nativo)
agenda_widget.xmlLayout visual do widget
agenda_widget_info.xmlConfiguração para o sistema

8️⃣ LAYOUT DO WIDGET (XML)

📁 res/layout/agenda_widget.xml

❗ REGRAS DE WIDGET

  • TODA View precisa de layout_width e layout_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
)
)