Bildirim tercihleri — RTK Query
Notification preferences mobil API slice
Mobil Entegrasyon Dokumani - Bildirim Tercihleri (RTK Query)
Migration rehberi
Yapılandırma sırası için Migration şablonu. Yeni düzenlemelerde Amaç → Önkoşullar → Endpoint → Request/Response → Hata kodları → Client adımları → İlgili sayfalar bölümlerini tercih edin.
Bu doküman, backend tarafında eklenen kategori bazli bildirim tercihlerini mobil uygulamada RTK Query ile entegre etmek için hazırlandı.
1) Kapsam
Bu doküman su akislari kapsar:
- Kullanıcı bildirim ayarlarini çekme
- Kullanıcı bildirim ayarlarini guncelleme
- Bildirim listesini kategoriye gore filtreleme
- Bildirimi okundu olarak isaretleme
Bu dokümanın odağı UI tasarımı değil, veri ve API entegrasyonudur.
2) Backend sozlesmesi (ozet)
2.1 User Settings
GET /user-settingsPATCH /user-settings
Kritik alanlar:
notifications: master switchliveStreamNotificationspaymentNotificationsfollowNotificationsmessageNotificationsfollowingActivityNotifications
Not:
notifications = falseise tüm kategoriler backend tarafında kapali kabul edilir.- Kategori alanlari eski kullanıcılarda eksikse backend
truefallback uygular.
2.2 Notifications list
GET /api/v2/notifications
Query parametreleri:
type?: stringcategory?: 'live_stream' | 'payments' | 'follows' | 'messages' | 'following_activity'read?: booleanisRead?: boolean(legacy)page?: number(varsayilan:1)limit?: number(varsayilan:20, maksimum:100)
2.3 Mark as read
PATCH /notifications/:id/read- Body:
{ "isRead": true }
3) Type tanimlari (mobil)
export type NotificationCategory =
| 'live_stream'
| 'payments'
| 'follows'
| 'messages'
| 'following_activity';
export type UserSettings = {
_id: string;
notifications: boolean;
totalMinutesVisibility: boolean;
totalFundVisibility: boolean;
liveStreamNotifications: boolean;
paymentNotifications: boolean;
followNotifications: boolean;
messageNotifications: boolean;
followingActivityNotifications: boolean;
createdAt: string;
updatedAt: string;
};
export type NotificationItem = {
id: string;
userId: string;
type: string;
category: NotificationCategory;
title: string;
message?: string;
metadata?: Record<string, unknown>;
read: boolean;
createdAt: string;
updatedAt: string;
};
export type PaginatedResponse<T> = {
list: T[];
pagination: {
currentPage: number;
totalPages: number;
totalItems: number;
itemsPerPage: number;
hasNextPage: boolean;
hasPrevPage: boolean;
};
};
export type BaseResponseDto<T> = {
isSuccess: boolean;
statusCode: number;
data: T;
errors?: string[];
timestamp: string;
};4) RTK Query - baseApi varsayimi
Ornek:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import type { RootState } from '../store';
export const baseApi = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: `${process.env.EXPO_PUBLIC_API_URL}/api`,
prepareHeaders: (headers, { getState }) => {
const token = (getState() as RootState).auth.accessToken;
if (token) headers.set('Authorization', `Bearer ${token}`);
return headers;
},
}),
tagTypes: ['UserSettings', 'Notifications'],
endpoints: () => ({}),
});5) RTK Query - settings endpointleri
src/store/api/notificationSettingsApi.ts
import { baseApi } from './baseApi';
import type { BaseResponseDto, UserSettings } from '@/types/notifications';
type UpdateUserSettingsPayload = Partial<
Pick<
UserSettings,
| 'notifications'
| 'liveStreamNotifications'
| 'paymentNotifications'
| 'followNotifications'
| 'messageNotifications'
| 'followingActivityNotifications'
>
>;
const unwrap = <T>(response: BaseResponseDto<T> | T): T => {
if (response && typeof response === 'object' && 'data' in (response as any)) {
return (response as BaseResponseDto<T>).data;
}
return response as T;
};
export const notificationSettingsApi = baseApi.injectEndpoints({
endpoints: (builder) => ({
getUserSettings: builder.query<UserSettings, void>({
query: () => ({ url: '/user-settings' }),
transformResponse: (response: BaseResponseDto<UserSettings> | UserSettings) =>
unwrap(response),
providesTags: [{ type: 'UserSettings', id: 'ME' }],
}),
updateUserSettings: builder.mutation<UserSettings, UpdateUserSettingsPayload>({
query: (body) => ({
url: '/user-settings',
method: 'PATCH',
body,
}),
transformResponse: (response: BaseResponseDto<UserSettings> | UserSettings) =>
unwrap(response),
invalidatesTags: [{ type: 'UserSettings', id: 'ME' }],
async onQueryStarted(arg, { dispatch, queryFulfilled }) {
const patch = dispatch(
notificationSettingsApi.util.updateQueryData('getUserSettings', undefined, (draft) => {
Object.assign(draft, arg);
}),
);
try {
await queryFulfilled;
} catch {
patch.undo();
}
},
}),
}),
});
export const {
useGetUserSettingsQuery,
useUpdateUserSettingsMutation,
} = notificationSettingsApi;6) RTK Query - notifications endpointleri
src/store/api/notificationsApi.ts
import { baseApi } from './baseApi';
import type { BaseResponseDto, NotificationCategory, NotificationItem } from '@/types/notifications';
type NotificationsQuery = {
category?: NotificationCategory;
read?: boolean;
type?: string;
page?: number;
limit?: number;
};
type NotificationsListResponse = {
list: NotificationItem[];
pagination: {
currentPage: number;
totalPages: number;
totalItems: number;
itemsPerPage: number;
hasNextPage: boolean;
hasPrevPage: boolean;
};
};
const unwrap = <T>(response: BaseResponseDto<T> | T): T => {
if (response && typeof response === 'object' && 'data' in (response as any)) {
return (response as BaseResponseDto<T>).data;
}
return response as T;
};
export const notificationsApi = baseApi.injectEndpoints({
endpoints: (builder) => ({
getNotifications: builder.query<NotificationsListResponse, NotificationsQuery | void>({
query: (params) => ({
url: '/v2/notifications',
params,
}),
transformResponse: (
response: BaseResponseDto<NotificationsListResponse> | NotificationsListResponse,
) =>
unwrap(response),
providesTags: (result) =>
result
? [
...result.list.map((n) => ({ type: 'Notifications' as const, id: n.id })),
{ type: 'Notifications' as const, id: 'LIST' },
]
: [{ type: 'Notifications' as const, id: 'LIST' }],
}),
markNotificationRead: builder.mutation<NotificationItem, { id: string; isRead: boolean }>({
query: ({ id, isRead }) => ({
url: `/notifications/${id}/read`,
method: 'PATCH',
body: { isRead },
}),
transformResponse: (response: BaseResponseDto<NotificationItem> | NotificationItem) =>
unwrap(response),
invalidatesTags: (_result, _err, arg) => [
{ type: 'Notifications', id: arg.id },
{ type: 'Notifications', id: 'LIST' },
],
}),
}),
});
export const {
useGetNotificationsQuery,
useMarkNotificationReadMutation,
} = notificationsApi;7) UI entegrasyon akisi (onerilen)
7.1 Settings screen
- Ekran acilisinda
useGetUserSettingsQuery()cagir. - Master toggle
notificationsalanina baglanir. - Kategori toggle'lari su alanlara baglanir:
liveStreamNotificationspaymentNotificationsfollowNotificationsmessageNotificationsfollowingActivityNotifications
Toggle davranisi:
- Master kapatilirsa sadece
notifications: falsepatch et. - Master acilirsa sadece
notifications: truepatch et. - Kategori toggle'da tek alan patch et.
- Master kapalıyken kategori toggle UI tarafında disabled göster.
Ornek handler:
const [updateSettings, { isLoading: isSaving }] = useUpdateUserSettingsMutation();
const onToggleMaster = async (value: boolean) => {
await updateSettings({ notifications: value }).unwrap();
};
const onToggleCategory = async (
key:
| 'liveStreamNotifications'
| 'paymentNotifications'
| 'followNotifications'
| 'messageNotifications'
| 'followingActivityNotifications',
value: boolean,
) => {
await updateSettings({ [key]: value }).unwrap();
};7.2 Notification center
- Tüm liste:
useGetNotificationsQuery({ page: 1, limit: 20 }) - Kategori tablari:
useGetNotificationsQuery({ category: 'messages', page: 1, limit: 20 })gibi - Okunmamis liste:
useGetNotificationsQuery({ read: false, page: 1, limit: 20 }) - Listeye ulasim:
data?.list, pagination metasi:data?.pagination - Kart tiklandiginda
markNotificationRead({ id, isRead: true })
8) Push izni ve device token notu
Kategori ayarlari backend tarafında filtreleme yapar. Bu nedenle device token kayit akışınız değişmek zorunda değil.
Onerilen:
- Uygulama acilisinda izin durumuna gore token varsa
POST /notifications/device-token - Timezone update yine ayni endpoint ile devam eder
9) Hata durumlari ve UX
- Patch hatasinda optimistic update geri alınmali
- Toggle basina loading göstermek için
isSavingveya local pending state kullanın - Offline durumda queue veya retry stratejisi ekleyin
- 401 durumda auth yenileme/logout mekanizmasi devreye girmeli
10) QA checklist
- Master kapali iken kategori toggle UI disabled mi
- Master acik iken kategori toggle'lar dogru kaydoluyor mu
- Uygulama yeniden acilinca ayarlar dogru hydrate oluyor mu
GET /api/v2/notifications?category=...&page=1&limit=20dogru liste donduruyor mu- Okundu isaretleme sonrasi liste state'i dogru mu
- Push izni acik/kapali durumunda app crash olmadan ayarlar calisiyor mu
11) Kisa migration notu
Eger mobilde eski local modelde sadece notifications tutuluyorsa:
- Yeni release ile local modelinize 5 kategori alanini ekleyin
- Ilk acilista backend
GET /user-settingssonucu local state'i overwrite etsin - Hardcoded default yerine backend değerini source of truth kabul edin