Skip to content

Commit 8be8618

Browse files
committed
Add FCM push notification support
Enable Firebase Cloud Messaging and token management plus related UI/navigation and repository fixes. Adds Firebase config and wiring (android/app/google-services.json, .firebaserc, firebase.json, lib/firebase_options.dart), Gradle plugin checks, and README instructions for setup and Supabase deployment. Implements PushNotificationService (init, background handler, permission/token handling), PushTokenRepository, and PushNotificationCoordinator (register/clear tokens on auth changes). Adds Supabase function + migration (supabase/functions/push-fcm, supabase/migrations/20260505140000_user_fcm_tokens.sql) and updates functions config. Also: include firebase_core/firebase_messaging/permission_handler deps, register background handler & optional token logging in main.dart, add mainLayout tab request provider and listener (navigation integration), improve follow repo fetching (chunked profile/pet fetch helpers) and follow controller invalidations, and refine pet followers screen validation/error messages.
1 parent c137ad1 commit 8be8618

24 files changed

Lines changed: 586 additions & 31 deletions

.firebaserc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"projects": {
3+
"default": "petfolio-197e6"
4+
}
5+
}

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,19 @@ PetFolio is a pet social and marketplace platform that allows users to connect w
9090
- Mark all as read on open
9191
- Tap notification → navigate to relevant entity (post, order, messages)
9292

93+
#### Push notifications (FCM, Android)
94+
When the app is in the background or killed, **in-app** Supabase Realtime updates stop; **Firebase Cloud Messaging** delivers system notifications instead.
95+
96+
**Client (this repo)**
97+
- After login, the app registers an FCM device token and upserts it into Supabase `user_fcm_tokens` (`lib/services/push_notification_service.dart`, `lib/repositories/push_token_repository.dart`).
98+
- Android 13+ uses the existing `POST_NOTIFICATIONS` permission plus a runtime prompt via `permission_handler`.
99+
- Firebase is wired for **PetFolio** (`petfolio-197e6`): `firebase.json`, `android/app/google-services.json`, and `lib/firebase_options.dart` are generated via [FlutterFire CLI](https://firebase.flutter.dev/docs/cli/configure) (`flutterfire configure --project=petfolio-197e6 …`). Re-run that command if you clone on a new machine or add iOS/Web. `google-services.json.example` remains as a template reference only.
100+
101+
**Server (Supabase)**
102+
1. Apply migration `supabase/migrations/20260505140000_user_fcm_tokens.sql` (`supabase db push` or Dashboard SQL).
103+
2. Set Edge secret `FIREBASE_SERVICE_ACCOUNT_JSON` to the **minified** JSON from Firebase Console → Project settings → Service accounts → Generate new private key.
104+
3. Deploy `supabase functions deploy push-fcm` and create a **Database Webhook** on `public.notifications` **INSERT** targeting that function (same pattern as [Supabase FCM guide](https://supabase.com/docs/guides/functions/examples/push-notifications?platform=fcm)).
105+
93106
---
94107

95108
### 🐕 Pet Care Diary (Core Feature)

android/app/build.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,10 @@ android {
4242
flutter {
4343
source = "../.."
4444
}
45+
46+
// FCM: add `android/app/google-services.json` from Firebase Console (Android app
47+
// with package `com.example.pet_dating_app`). Without it, the app builds but
48+
// Firebase.initializeApp / device tokens will not work.
49+
if (file("google-services.json").exists()) {
50+
apply(plugin = "com.google.gms.google-services")
51+
}

android/app/google-services.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"project_info": {
3+
"project_number": "675318892178",
4+
"project_id": "petfolio-197e6",
5+
"storage_bucket": "petfolio-197e6.firebasestorage.app"
6+
},
7+
"client": [
8+
{
9+
"client_info": {
10+
"mobilesdk_app_id": "1:675318892178:android:93c1b41620e286d3bec84b",
11+
"android_client_info": {
12+
"package_name": "com.example.pet_dating_app"
13+
}
14+
},
15+
"oauth_client": [],
16+
"api_key": [
17+
{
18+
"current_key": "AIzaSyBWaGVK1Q-QQauS8EoQlAWrKrMdc4rrATQ"
19+
}
20+
],
21+
"services": {
22+
"appinvite_service": {
23+
"other_platform_oauth_client": []
24+
}
25+
}
26+
}
27+
],
28+
"configuration_version": "1"
29+
}
1.32 KB
Binary file not shown.

android/settings.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ plugins {
2121
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
2222
id("com.android.application") version "8.9.1" apply false
2323
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
24+
id("com.google.gms.google-services") version "4.4.2" apply false
2425
}
2526

2627
include(":app")

docs/USER_STORIES_platform_social_matching_commerce_care.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ flowchart TB
334334
1. **Prioritize epics** E1 → E2 → E4 (or E3 first if matching is the wedge).
335335
2. **Attach metrics** per epic (activation, D7 retention, GMV, care log completion rate, report rate).
336336
3. **Decompose** each US into technical tasks (API, RLS policies, UI, analytics).
337-
4. **Compliance review** for E5/E8 before promising clinical workflows.
337+
4. **Compliance review** for E5/E7 before promising clinical workflows.
338338

339339
---
340340

firebase.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"flutter":{"platforms":{"android":{"default":{"projectId":"petfolio-197e6","appId":"1:675318892178:android:93c1b41620e286d3bec84b","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"petfolio-197e6","configurations":{"android":"1:675318892178:android:93c1b41620e286d3bec84b"}}}}}}

lib/controllers/follow_controller.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@ class FollowController extends Notifier<void> {
8181
// Invalidate related providers so the UI refreshes
8282
ref.invalidate(isFollowingOwnerProvider(ownerId));
8383
ref.invalidate(ownerFollowerCountProvider(ownerId));
84+
ref.invalidate(ownerFollowersListProvider(ownerId));
8485
ref.invalidate(followingCountProvider(userId));
86+
ref.invalidate(followingListProvider(userId));
8587
} catch (e) {
8688
debugPrint('toggleFollowOwner error: $e');
8789
}
@@ -127,7 +129,9 @@ class FollowController extends Notifier<void> {
127129
// Invalidate related providers
128130
ref.invalidate(isFollowingPetProvider(petId));
129131
ref.invalidate(petFollowerCountProvider(petId));
132+
ref.invalidate(petFollowersListProvider(petId));
130133
ref.invalidate(followingCountProvider(userId));
134+
ref.invalidate(followingListProvider(userId));
131135
} catch (e) {
132136
debugPrint('toggleFollowPet error: $e');
133137
}

lib/controllers/pet_controller.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,17 @@ class ProfilePetNavigation extends Notifier<String?> {
208208
final profilePetNavigationProvider =
209209
NotifierProvider<ProfilePetNavigation, String?>(
210210
() => ProfilePetNavigation());
211+
212+
/// [MainLayout] listens and switches its bottom tab to the requested index
213+
/// (e.g. 4 for Profile), then clears the value.
214+
class MainLayoutTabRequest extends Notifier<int?> {
215+
@override
216+
int? build() => null;
217+
218+
void request(int index) => state = index;
219+
220+
void clear() => state = null;
221+
}
222+
223+
final mainLayoutTabRequestProvider =
224+
NotifierProvider<MainLayoutTabRequest, int?>(MainLayoutTabRequest.new);

0 commit comments

Comments
 (0)