
React Native vs Native (Swift/Kotlin) 2026-ban: mikor melyiket válaszd
Performance benchmarks, developer experience, ecosystem és 5 use case ajánlás. React Native vagy native — döntsd el szakmai szempontok mentén.
A push notification a felhasználói retention top-3 eszköze — vagy a top-3 ok a deinstall-ra. 10 tipikus hiba és a helyes implementáció kóddal.

Push lifecycle
Késleltetett opt-in az első értelmes user-akció után. In-app magyarázat előtte, +20-30 pp opt-in rate.
Login után user_id + device_id + push_token rögzítés Postgres-be. Refresh minden login-nál.
Frequency cap (max 3 / hét), time-zone-aware küldés 9-21 között, perszonalizált tartalom.
Delivery 95%+, open rate 5-15% promo / 30-50% transactional. Cohort-elemzés az opt-out rate-re.
A push notification a felhasználói retention egyik legfontosabb eszköze — de helytelen használata a deinstall #1 oka. A statisztikák brutális egyértelműek: a túl gyakori vagy irreleváns push az app-permission-revoke első három okának egyike, mind iOS-en, mind Android-on (Pushwoosh / Airship 2024 jelentések alapján).
Ebben a cikkben 10 tipikus hiba, amit szinte minden első mobil app fejlesztő elkövet, és a helyes implementáció. Konkrét kóddal, mindkét platformra. A példák Swift 6, Kotlin (Jetpack Compose) és React Native (Expo Notifications) nyelven.
Néhány statisztika 2025-ös app-ipari riportokból:
A különbség drámai. A következő 10 hibában a 25-40% open rate közelébe juthatsz a 2-5% helyett.
Hiba: A user megnyitja az app-et, és az első képernyőn jön a „Engedélyezed a push értesítéseket?" prompt. iOS-en ezt egyszer kérdezheted — ha „No", soha többé nem tudsz kérni programmatically.
A felhasználó nem tudja, miért kell engedélyt adni — még semmit nem látott az app-ből. A „nem" arány ekkor 60-70%.
Helyes: Késleltesd a prompt-ot az első értelmes user-akciójig (pl. első sikeres bejelentkezés, vagy onboarding végén). Soft ask előtte: egy in-app képernyőn magyarázod, hogy mikor fogsz pingelni, és mi az értéke.
import UserNotifications
import UIKit
final class PushPermissionManager {
static let shared = PushPermissionManager()
func showSoftAskThenRequest(from vc: UIViewController) {
let alert = UIAlertController(
title: "Értesítések",
message: "Engedélyezed, hogy értesítsünk, amikor új ajánlat érkezik a számodra? Maximum heti 2-3 üzenet.",
preferredStyle: .alert,
)
alert.addAction(.init(title: "Nem most", style: .cancel))
alert.addAction(.init(title: "Engedélyezem", style: .default) { _ in
self.requestPermission()
})
vc.present(alert, animated: true)
}
private func requestPermission() {
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
if granted {
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
}
}
}
Android 13+ óta (API 33) a push permission runtime-permission lett, hasonlóan az iOS-hez:
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
@Composable
fun PushPermissionScreen(onGranted: () -> Unit) {
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission(),
) { granted ->
if (granted) onGranted()
}
Column { /* ... soft ask UI ... */ }
Button(onClick = {
launcher.launch(android.Manifest.permission.POST_NOTIFICATIONS)
}) {
Text("Engedélyezem")
}
}
import * as Notifications from 'expo-notifications';
async function requestPushPermission() {
const { status: existingStatus } = await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync();
finalStatus = status;
}
return finalStatus === 'granted';
}
A soft-ask + real-prompt két lépéses flow opt-in rate-et 50-60%-ról 70-80%-ra emeli.
Hiba: A push token-t csak az első app-launch-kor regisztrálod a backend-nek. Ha a user kijelentkezik és újra bejelentkezik (másik fiókba), a régi fiókra jönnek a push-ok.
Helyes: Token-refresh minden sikeres bejelentkezésnél. A backend tárolja: (user_id, device_id, push_token, platform, last_seen).
// Lifecycle: bejelentkezés
async function onUserLogin(userId: string) {
const token = await Notifications.getExpoPushTokenAsync();
await api.post('/push/register', {
user_id: userId,
device_id: await getDeviceId(),
push_token: token.data,
platform: Platform.OS,
});
}
// Lifecycle: kijelentkezés
async function onUserLogout(userId: string) {
await api.post('/push/unregister', {
user_id: userId,
device_id: await getDeviceId(),
});
}
CREATE TABLE push_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
device_id TEXT NOT NULL,
push_token TEXT NOT NULL,
platform TEXT CHECK (platform IN ('ios', 'android', 'web')),
created_at TIMESTAMP DEFAULT NOW(),
last_seen TIMESTAMP DEFAULT NOW(),
UNIQUE(user_id, device_id)
);
CREATE INDEX idx_push_user ON push_tokens(user_id);
CREATE INDEX idx_push_token ON push_tokens(push_token);
A device_id-t MMKV (RN) vagy SharedPreferences (Android) tárolva, app-installation-szintű egyedi azonosító.
Hiba: „Új akció! Nézd meg most." → a user 3 nap után némítja az app-et.
A generic broadcast push az open rate-et 2-5%-ra húzza. Ami azt jelenti, hogy 1000 user-ből 50 megnyitja — a többi vagy ignorálja, vagy bosszankodik.
Helyes: Perszonalizált, kontextusos üzenetek a felhasználói viselkedés alapján.
Generic: "Új akció! Nézd meg most." Open rate: 3%
Perszonalizált: "Az ABC-123 alkatrész, amit
nézegettél, 15% kedvezménnyel
elérhető 24 órán át." Open rate: 28%
| Szegmens | Trigger | Üzenet-style |
|---|---|---|
| New user (< 7 nap) | Onboarding step missing | „Töltsd ki a profilodat 2 perc alatt — ezt nyered cserébe..." |
| Active user (heti 3+ session) | Új feature release | „Új funkció: X. Próbáld ki most." |
| Re-engagement (14+ nap inaktív) | Personalized comeback | „Az ABC-123 termékre 20% kedvezmény, kifejezetten neked." |
| Cart abandon | 1, 24, 72 óra cart-ban | „A kosaradban van 3 termék — folytasd a vásárlást." |
type PushPayload = {
user_id: string;
template: 'cart_abandon' | 'new_feature' | 'personalized_promo';
context: Record<string, string>;
};
async function sendPersonalizedPush(payload: PushPayload) {
const user = await db.users.findById(payload.user_id);
const template = TEMPLATES[payload.template];
const message = template(user, payload.context);
const tokens = await db.pushTokens.findByUserId(payload.user_id);
for (const t of tokens) {
await pushProvider.send(t.push_token, message);
}
}
Hiba: Európai szerver UTC-n fut, a user iPhone-ján 23:00-kor reggeli üdvözlés érkezik.
A „Jó reggelt! Itt vannak a mai ajánlatok" 23:35-kor frappáns. A user másnap reggel deinstall-ol.
Helyes: User-szintű time zone tárolása. Send-time optimization: az adott user history alapján melyik időben nyitja meg a push-okat.
// Backend: send window
async function sendInTimeWindow(userId: string, payload: any) {
const user = await db.users.findById(userId);
const userLocalHour = getUserLocalHour(user.timezone);
if (userLocalHour < 9 || userLocalHour > 21) {
const nextSendTime = scheduleForNextMorning(user.timezone);
await scheduler.scheduleAt(nextSendTime, () => {
pushProvider.send(userId, payload);
});
return { status: 'scheduled', sendAt: nextSendTime };
}
return pushProvider.send(userId, payload);
}
ALTER TABLE users ADD COLUMN timezone TEXT DEFAULT 'Europe/Budapest';
-- App-side: minden launchkor frissítsd
UPDATE users SET timezone = $1 WHERE id = $2;
Az adott user push-open history-ját elemezve mérhető a legjobb send-time:
SELECT
user_id,
EXTRACT(HOUR FROM (opened_at AT TIME ZONE timezone)) AS open_hour,
COUNT(*) AS open_count
FROM push_events
WHERE event_type = 'opened'
GROUP BY user_id, EXTRACT(HOUR FROM (opened_at AT TIME ZONE timezone))
ORDER BY open_count DESC
LIMIT 1;
Az 1-óra-ablak optimalizáció +30-50% open rate-et hoz az „every user same time" stratégiához képest.
Hiba: A user kattint a notification-re („Az X termék elérhető"), és az app a home-screen-re viszi, nem a termék oldalra.
A user 2-3 ilyen tévedés után már nem kattint a push-okra. „Úgyis nem azt mutatja, amit ígér."
Helyes: Deep link minden notification-be. iOS: Universal Links, Android: App Links. A payload tartalmaz egy route mezőt, az app launch-kor erre navigál.
{
"to": "ExponentPushToken[xxx]",
"notification": {
"title": "Az ABC-123 termék elérhető",
"body": "Kattints a részletekért — 15% kedvezmény",
"sound": "default",
"badge": 1
},
"data": {
"route": "product/abc-123",
"campaign_id": "promo_q2_2026",
"user_segment": "active_cart_abandon"
}
}
import * as Notifications from 'expo-notifications';
import { router } from 'expo-router';
useEffect(() => {
const subscription = Notifications.addNotificationResponseReceivedListener(
(response) => {
const route = response.notification.request.content.data?.route;
const campaignId = response.notification.request.content.data?.campaign_id;
// Track open
analytics.track('push_opened', { campaign_id: campaignId });
if (route) {
router.push(`/${route}`);
}
},
);
return () => subscription.remove();
}, []);
// In AppDelegate
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else { return false }
return navigationCoordinator.handle(url: url)
}
Hiba: Marketing-csapat napi 2-3 push-ot küld minden user-nek. Egy hónap után deinstall rate 25%+.
A „minél több push, annál több revenue" gondolkodás 2026-ban már bizonyítottan rossz. A felhasználói deinstall hosszú távon több kárt okoz, mint amennyi rövid távú click-through-t hoz.
Helyes: Frequency cap: max 3 push / hét per user. A többi marketing-üzenet emailbe, in-app message-be vagy nem küldött.
async function canSendPush(userId: string): Promise<boolean> {
const lastWeek = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
const sentCount = await db.pushEvents.count({
user_id: userId,
event_type: 'sent',
sent_at: { gte: lastWeek },
});
return sentCount < 3;
}
Néha kell magas-prioritás push, ami felülírja a frequency cap-et:
type Priority = 'transactional' | 'promotional';
async function sendWithFrequencyCap(
userId: string,
payload: PushPayload,
priority: Priority = 'promotional',
) {
if (priority === 'transactional') {
return pushProvider.send(userId, payload); // bypass cap
}
const canSend = await canSendPush(userId);
if (!canSend) {
await db.queues.add('email', { user_id: userId, payload });
return { status: 'fallback_to_email' };
}
return pushProvider.send(userId, payload);
}
A „rendelés szállítva" push transactional → mindig megy. Az „új termék" promotional → frequency cap alatt.
Tipp: Engedélyezz user-szintű frequency control-t a settings-ben. A „normál" (default), „heti 1" és „csak transactional" opciók — a power-user-eknek finomhangolás, a vendor opt-in rate-nek viszont védelem.
Hiba: A user kikapcsolja a push-okat OS-szinten. Az app továbbra is bombázza a backend-et token-refresh kérésekkel, és a backend továbbra is push-okat küld (amelyek nem érkeznek meg, ami szilenciósan elveszik).
Helyes: Check minden launch-kor: UNUserNotificationCenter.current().getNotificationSettings() iOS-en, NotificationManagerCompat.from(context).areNotificationsEnabled() Android-on. Ha disabled, a backend-en flag user-re: push_enabled = false. Email-flow átveszi.
// iOS
UNUserNotificationCenter.current().getNotificationSettings { settings in
let isEnabled = settings.authorizationStatus == .authorized
api.updatePushStatus(enabled: isEnabled)
}
// Android
val isEnabled = NotificationManagerCompat.from(context).areNotificationsEnabled()
api.updatePushStatus(enabled = isEnabled)
Ha a push disabled:
Hiba: 200 karakteres üzenet, ami a notification panel-en csonkolva van: „Új akció a termékkategóriában, ahol múlt héten néze..."
Az iOS notification banner ~80-100 karaktert mutat, Android ~80 karaktert (eszközfüggő). A csonkolt szöveg nem hat.
Helyes: Title 30 char, body 80 char alatt. Push körüli „bug": minden mobil OS különböző hosszt vág le; tervezd a legrövidebbre.
Hiba:
Title: "Új akció a termékkategóriában, ahol múlt héten nézegettél!"
Body: "Az ABC-123 alkatrész, amelyet még az előző héten néztél meg, most 15% kedvezménnyel elérhető a következő 24 órában. Siess, mert a készlet korlátozott!"
Helyes:
Title: "ABC-123 — 15% kedvezmény" (29 char)
Body: "24 óráig 15% off az alkatrészedre" (35 char)
| Variant | Title length | Open rate | Conversion |
|---|---|---|---|
| Long-form | 60+ char | 4.2% | 0.8% |
| Short-form | 25 char | 12.5% | 2.1% |
A rövid forma 3x jobb open rate-et hoz a mérhető átlagos eset szerint.
Hiba: Minden push-hoz nagy 1MB-os képet csatolsz. iOS notification service extension timeout (30s), és a kép nem jelenik meg.
A felhasználó push-ot lát, de „attachment failed" vagy üres. A custom UI-ja megsemmisíti a saját notification-formátumát.
Helyes: Image-attachment max 300KB, CDN-en, jól optimalizálva. Csak releváns kontextusban (termék-image, rendelés-image). 80% notification image nélkül megy ki.
// NotificationServiceExtension/NotificationService.swift
import UserNotifications
class NotificationService: UNNotificationServiceExtension {
override func didReceive(_ request: UNNotificationRequest,
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
guard let bestAttempt = request.content.mutableCopy() as? UNMutableNotificationContent,
let imageUrlString = request.content.userInfo["image_url"] as? String,
let imageUrl = URL(string: imageUrlString) else {
contentHandler(request.content)
return
}
URLSession.shared.downloadTask(with: imageUrl) { localUrl, _, _ in
guard let localUrl = localUrl else {
contentHandler(bestAttempt)
return
}
if let attachment = try? UNNotificationAttachment(
identifier: "image",
url: localUrl,
options: nil,
) {
bestAttempt.attachments = [attachment]
}
contentHandler(bestAttempt)
}.resume()
}
}
Hiba: Sendeled a push-t, és nem tudod ki nyitja meg, ki ignoreál, ki némít.
A push-kampány optimalizálás csak akkor működik, ha méred. A measurement nélkül a marketing-csapat „érzés-alapon" küld.
Helyes: OneSignal vagy Firebase Cloud Messaging analytics. KPI-k:
CREATE TABLE push_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
campaign_id TEXT,
event_type TEXT CHECK (event_type IN ('sent', 'delivered', 'opened', 'converted', 'dismissed')),
timestamp TIMESTAMP DEFAULT NOW(),
metadata JSONB
);
CREATE INDEX idx_push_events_user ON push_events(user_id, timestamp DESC);
CREATE INDEX idx_push_events_campaign ON push_events(campaign_id, event_type);
Hetente induló user-szegmens push-érzékenysége. Az „opted out within 7 days" kohort jelzi a túlsendingest.
WITH user_cohorts AS (
SELECT
user_id,
DATE_TRUNC('week', created_at) AS cohort_week
FROM users
),
opt_outs AS (
SELECT
user_id,
MAX(timestamp) AS opt_out_time
FROM push_events
WHERE event_type = 'dismissed'
AND metadata->>'reason' = 'system_disabled'
GROUP BY user_id
)
SELECT
uc.cohort_week,
COUNT(uc.user_id) AS total_users,
COUNT(oo.user_id) AS opted_out,
ROUND(100.0 * COUNT(oo.user_id) / COUNT(uc.user_id), 2) AS opt_out_rate
FROM user_cohorts uc
LEFT JOIN opt_outs oo ON uc.user_id = oo.user_id
GROUP BY uc.cohort_week
ORDER BY uc.cohort_week;
Az opt-out rate trend kritikus mérőszám.
Ingyenes, scalable, mind iOS mind Android-ra. Default választás 90% projektre.
Előnyök:
Hátrányok:
Managed UI, A/B testing, segmentation. Free tier 10k user-ig, fizetős utána. Marketing-fókuszú teamekre.
Előnyök:
Hátrányok:
Native iOS, ha a FCM réteg nem indokolt.
Előnyök:
Hátrányok:
| Use case | Provider |
|---|---|
| MVP, ingyenes | FCM |
| Marketing-heavy team | OneSignal |
| Enterprise, multi-channel | OneSignal vagy Airship |
| iOS-only, low-volume | APNs direkt |
| Cross-platform B2C, magas-volume | FCM + saját analytics |
A push-üzenetekhez kötelező az A/B testing. Két variánst egyszerre küldve 1-2 hét alatt szignifikáns eredmény.
async function sendABTestPush(userIds: string[], variantA: Payload, variantB: Payload) {
const shuffled = userIds.sort(() => Math.random() - 0.5);
const half = Math.floor(shuffled.length / 2);
await Promise.all([
...shuffled.slice(0, half).map(uid => sendWithTracking(uid, variantA, 'variant_a')),
...shuffled.slice(half).map(uid => sendWithTracking(uid, variantB, 'variant_b')),
]);
}
Mérendő:
A „winner" variant 2-3 nap után az új default.
Témához kapcsolódó saját cikkeink: App Store & Play Store deployment 2026 — release pipeline. React Native vs Native 2026 — platform-választás. Mobil app GDPR compliance — consent management, ami a push permission-ra is hat.
A push notification eszköz erős, de óvatos. A 10 fenti hiba mindegyike egy realiter konkrét projekt-elhalása. A jó push-flow visszafogott, time-zone-aware és kattintásra deeplink-el.
A push-stratégia első alkalommal nehéz, a 3-4. iteration után rutin lesz. Az első 3 hónap a tanulás fázisa — méreted az open rate-et, opt-out rate-et, és iteratívan finomhangolod a frequency-t és a szegmentációt.
Ha mobil app projektet tervezel, beszéljük át a push és retention stratégiát — gyakran nem a feature-set hiányzik, hanem a felhasználói flow finomhangolása. Az első 90 nap monitor-fázis bekonfigurálása része a deliverable-nek.
A szerzőről
Corevanix Kft.
Tech partner
Modern tech partner — SAP/ERP, webfejlesztés, AI automatizáció és mobil app fejlesztés egy szakmai csapatban. KKV-tól enterprise projektig.
LinkedIn →
Performance benchmarks, developer experience, ecosystem és 5 use case ajánlás. React Native vagy native — döntsd el szakmai szempontok mentén.

Lépésről lépésre guide az iOS App Store és Google Play Store deployment-hez. Account, certificate, review folyamat és Fastlane automation.

GDPR alapok mobilon, consent management, IDFA / GAID kezelés, analytics tools és audit checklist. A 2026-os compliance-alapok rövid összefoglalója.