Giriş: Expo ile Widget Geliştirmenin Yeni Dönemi
Expo ekibi sonunda beklenen hamleyi yaptı — expo-widgets kütüphanesinin alpha sürümü Mart 2026 itibarıyla resmi olarak yayında. React Native geliştiricileri olarak hepimizin hayali olan şey gerçekleşiyor: Swift veya SwiftUI kodu yazmadan iOS ana ekran widget'ları ve Live Activities oluşturabiliyoruz. Expo SDK 55 ile gelen bu yenilik, açıkçası mobil geliştirme sürecimizde ciddi bir dönüm noktası.
İstatistiklere bakınca neden bu kadar önemli olduğu net ortaya çıkıyor. iOS kullanıcılarının %80,7'si en az bir widget kullanıyor. Yani kullanıcılarınızın büyük çoğunluğu zaten widget'larla etkileşim halinde — ve artık siz de Expo projenize bu özelliği kolayca entegre edebilirsiniz.
expo-widgets Nasıl Çalışır?
Şimdi, burada kritik bir detay var. Widget uzantıları render sırasında React Native çalıştıramaz. Sistem tarafından talep üzerine, oldukça sıkı bir zaman bütçesiyle (ve potansiyel olarak uygulamanız kapalıyken bile) render edilirler. Bu yüzden düzenin native terimlerle ifade edilmesi şart.
Peki bu pratikte nasıl işliyor?
@expo/ui kütüphanesi, doğrudan SwiftUI primitiflerine eşlenen Text, VStack, HStack ve Image gibi React bileşenleri sunuyor. Sistem bir widget zaman çizelgesi istediğinde, bileşeniniz ayrı bir JS runtime'da çalışır ve bir @expo/ui düzen ağacı üretir. Native taraf da bu açıklamayı kullanarak SwiftUI görünümleriyle arayüzü yeniden oluşturur.
Kısacası siz React bileşeni yazıyorsunuz, arka planda SwiftUI render işini hallediyor. Oldukça zarif bir çözüm.
Kurulum ve Yapılandırma
Gereksinimler
Başlamadan önce elinizde şunların olması gerekiyor:
- Expo SDK 55 veya üzeri
- React Native 0.83+
- iOS geliştirme ortamı (Xcode)
- Development build (Expo Go desteklenmez — bu önemli)
- New Architecture etkin (SDK 55'te zaten varsayılan olarak zorunlu)
Paket Kurulumu
Kurulum tek satır:
npx expo install expo-widgets
app.json Yapılandırması
Config plugin'i app.json dosyanıza ekleyin. Bu plugin, prebuild sırasında Widget Extension hedefini otomatik oluşturur, App Group'u yapılandırır ve gerekli tüm native dosyaları sizin yerinize halleder.
{
"expo": {
"plugins": [
[
"expo-widgets",
{
"bundleIdentifier": "com.myapp.widgets",
"groupIdentifier": "group.com.myapp",
"enablePushNotifications": false,
"widgets": [
{
"name": "HavaDurumuWidget",
"displayName": "Hava Durumu",
"description": "Anlık hava durumu bilgisi",
"supportedFamilies": [
"systemSmall",
"systemMedium",
"systemLarge"
]
}
]
}
]
]
}
}
Yapılandırma Parametreleri
Her parametrenin ne işe yaradığına kısaca bakalım:
- bundleIdentifier: Widget uzantısının bundle kimliği. Belirtmezseniz varsayılan olarak
<appId>.ExpoWidgetsTargetkullanılır. - groupIdentifier: Uygulama ile widget arasında veri paylaşımı sağlayan App Group kimliği.
- enablePushNotifications: Live Activities için APNs push bildirimlerini etkinleştirir.
- widgets: Widget tanımlarının dizisi. Her widget için
name,displayName,descriptionvesupportedFamiliesbelirtmeniz gerekiyor.
Desteklenen Widget Boyutları
iOS farklı boyutlarda widget'lar sunuyor ve uygulamanızın ihtiyacına göre bir veya birden fazla boyut destekleyebilirsiniz. İşte seçenekler:
- systemSmall: 2×2 ızgara — kompakt bilgi gösterimi için ideal
- systemMedium: 4×2 ızgara — biraz daha fazla detay sığdırabilirsiniz
- systemLarge: 4×4 ızgara — kapsamlı içerik gösterimi için uygun
- systemExtraLarge: 6×4 ızgara — yalnızca iPad'de kullanılabilir
- accessoryCircular: Kilit ekranı dairesel widget
- accessoryRectangular: Kilit ekranı dikdörtgen widget
- accessoryInline: Kilit ekranı satır içi metin widget'ı
İlk Widget'ınızı Oluşturma
Hadi gelin ilk widget'ımızı oluşturalım. Widget bileşeni yazarken dikkat etmeniz gereken birkaç temel kural var:
- Fonksiyon gövdesinin başına
'widget'yönergesi eklenmeli - Bileşen kendi kendine yeterli (self-contained) olmalı
- Görüntülenecek verileri props olarak almalı
createWidgetile dışa aktarılmalı
Basit Bir Sayaç Widget'ı
import { Button, Text, VStack } from '@expo/ui/swift-ui';
import { font, foregroundStyle } from '@expo/ui/swift-ui/modifiers';
import { createWidget, WidgetBase } from 'expo-widgets';
type SayacProps = { sayi: number };
const SayacWidget = (props: WidgetBase<SayacProps>) => {
'widget';
return (
<VStack spacing={8}>
<Text modifiers={[font({ size: 48 })]}>☕</Text>
<Text modifiers={[font({ size: 32, weight: 'bold' })]}>
{props.sayi}
</Text>
<Button
modifiers={[foregroundStyle('white')]}
label="+"
target="artir"
onPress={() => ({ sayi: props.sayi + 1 })}
/>
</VStack>
);
};
export default createWidget('SayacWidget', SayacWidget);
Burada createWidget fonksiyonuna verilen isim, app.json yapılandırmasındaki name alanıyla eşleşmeli. onPress handler'ı kısmi bir state güncelleme nesnesi döndürüyor; bu nesne mevcut state ile birleştirilerek widget yeniden render ediliyor — uygulama açılmasına gerek kalmıyor.
Duyarlı (Responsive) Widget Tasarımı
Widget bileşenlerine otomatik olarak enjekte edilen family prop'u sayesinde, aynı bileşen içinde farklı boyutlara uygun düzenler oluşturabilirsiniz. Bu gerçekten işleri kolaylaştıran bir özellik.
import { Text, VStack, HStack } from '@expo/ui/swift-ui';
import { font } from '@expo/ui/swift-ui/modifiers';
import { createWidget, WidgetBase } from 'expo-widgets';
type HavaDurumuProps = {
sicaklik: number;
durum: string;
sehir: string;
nem: number;
ruzgar: string;
};
const HavaDurumuWidget = (props: WidgetBase<HavaDurumuProps>) => {
'widget';
if (props.family === 'systemSmall') {
return (
<VStack spacing={4}>
<Text modifiers={[font({ size: 14 })]}>{props.sehir}</Text>
<Text modifiers={[font({ size: 36, weight: 'bold' })]}>
{props.sicaklik}°
</Text>
<Text>{props.durum}</Text>
</VStack>
);
}
return (
<HStack spacing={16}>
<VStack spacing={4}>
<Text modifiers={[font({ size: 14 })]}>{props.sehir}</Text>
<Text modifiers={[font({ size: 36, weight: 'bold' })]}>
{props.sicaklik}°
</Text>
</VStack>
<VStack spacing={4}>
<Text>{props.durum}</Text>
<Text>Nem: %{props.nem}</Text>
<Text>Rüzgar: {props.ruzgar}</Text>
</VStack>
</HStack>
);
};
export default createWidget('HavaDurumuWidget', HavaDurumuWidget);
Widget Zaman Çizelgesi Yönetimi
Widget'lar talep üzerine yenilenmez — bu çok önemli bir nokta. Siz iOS'a bir içerik zaman çizelgesi sağlıyorsunuz ve sistem ne zaman güncelleneceğine kendisi karar veriyor. expo-widgets iki temel güncelleme yöntemi sunuyor:
Anlık Güncelleme (Snapshot)
Widget'ı hemen tek bir girişle günceller. En basit yöntem:
import HavaDurumuWidget from './widgets/HavaDurumuWidget';
// Uygulamanızın herhangi bir yerinden
HavaDurumuWidget.updateSnapshot({
sicaklik: 24,
durum: 'Güneşli',
sehir: 'İstanbul',
nem: 65,
ruzgar: '15 km/s'
});
Zamanlanmış Güncelleme (Timeline)
Birden fazla zamanlanmış güncelleme planlayabilirsiniz. Sistem uygun zamanda doğru girişi otomatik olarak seçer — mesela hava durumu widget'ı için oldukça kullanışlı:
import HavaDurumuWidget from './widgets/HavaDurumuWidget';
HavaDurumuWidget.updateTimeline([
{
date: new Date(),
props: { sicaklik: 18, durum: 'Sabah Sisi', sehir: 'Ankara', nem: 80, ruzgar: '5 km/s' }
},
{
date: new Date(Date.now() + 4 * 3600000), // 4 saat sonra
props: { sicaklik: 24, durum: 'Güneşli', sehir: 'Ankara', nem: 55, ruzgar: '12 km/s' }
},
{
date: new Date(Date.now() + 10 * 3600000), // 10 saat sonra
props: { sicaklik: 15, durum: 'Parçalı Bulutlu', sehir: 'Ankara', nem: 70, ruzgar: '8 km/s' }
}
]);
Mevcut Zaman Çizelgesini Okuma
const timeline = await HavaDurumuWidget.getTimeline();
console.log('Aktif zaman çizelgesi:', timeline);
Widget'ı Yeniden Yükleme
HavaDurumuWidget.reload();
Live Activities ile Canlı İçerik Gösterimi
Live Activities, başlangıcı ve sonu belli olan süreçleri kilit ekranında ve Dynamic Island'da gerçek zamanlı olarak göstermek için kullanılıyor. Teslimat takibi, canlı skor, uçuş durumu gibi senaryolar için biçilmiş kaftan.
Önemli not: Live Activities en fazla 12 saat sürebilir. Bu sürenin sonunda iOS aktiviteyi otomatik olarak sonlandırır, buna dikkat edin.
Live Activity Bileşeni Oluşturma
Live Activity bileşeni, farklı görüntüleme alanlarını (slot) tanımlayan bir nesne döndürür. Her slot, farklı bir gösterim bağlamına karşılık geliyor:
import { Text, VStack, Image } from '@expo/ui/swift-ui';
import { font } from '@expo/ui/swift-ui/modifiers';
import { createLiveActivity } from 'expo-widgets';
type TeslimatProps = {
kalanDakika: number;
durum: string;
adres: string;
};
const TeslimatActivity = (props: TeslimatProps) => {
'widget';
return {
// Kilit ekranı banner'ı
banner: (
<VStack spacing={4}>
<Text modifiers={[font({ weight: 'bold' })]}>
Teslimat Takibi
</Text>
<Text>{props.durum}</Text>
<Text>Tahmini varış: {props.kalanDakika} dakika</Text>
<Text>{props.adres}</Text>
</VStack>
),
// Dynamic Island kompakt görünüm
compactLeading: (
<Image systemName="box.truck.fill" />
),
compactTrailing: (
<Text>{props.kalanDakika} dk</Text>
),
// Dynamic Island minimal görünüm
minimal: (
<Image systemName="box.truck.fill" />
),
// Dynamic Island genişletilmiş görünüm
expandedLeading: (
<VStack>
<Image systemName="box.truck.fill" />
<Text modifiers={[font({ size: 12 })]}>Teslimat</Text>
</VStack>
),
expandedTrailing: (
<VStack>
<Text modifiers={[font({ size: 24, weight: 'bold' })]}>
{props.kalanDakika} dk
</Text>
</VStack>
),
expandedBottom: (
<VStack>
<Text>{props.durum}</Text>
<Text modifiers={[font({ size: 12 })]}>{props.adres}</Text>
</VStack>
)
};
};
export default createLiveActivity('TeslimatActivity', TeslimatActivity);
Live Activity Görüntüleme Alanları
Her alanın ne işe yaradığını bilmek, doğru tasarım kararları vermenize yardımcı olur:
- banner: Kilit ekranı ve bildirim merkezinde tam genişlik görünüm (zorunlu alan)
- compactLeading / compactTrailing: Dynamic Island kompakt modunda sol ve sağ taraf
- minimal: Dynamic Island'ın en küçük gösterim modu
- expandedLeading / expandedTrailing / expandedCenter / expandedBottom: Dynamic Island genişletilmiş modundaki alanlar
Live Activity Yaşam Döngüsü
Bir Live Activity'nin başlatılması, güncellenmesi ve sonlandırılması oldukça basit:
import TeslimatActivity from './activities/TeslimatActivity';
// 1. Aktiviteyi başlat
const instance = TeslimatActivity.start(
{
kalanDakika: 30,
durum: 'Sipariş hazırlanıyor',
adres: 'Kadıköy, İstanbul'
},
'myapp://teslimat/123' // Derin bağlantı URL'si (opsiyonel)
);
// 2. Aktiviteyi güncelle
await instance.update({
kalanDakika: 15,
durum: 'Kurye yolda',
adres: 'Kadıköy, İstanbul'
});
// 3. Aktiviteyi sonlandır
await instance.end('default'); // 'default' veya 'immediate'
Aktif Aktiviteleri Listeleme
const aktifler = TeslimatActivity.getInstances();
console.log('Aktif teslimat sayısı:', aktifler.length);
Push Bildirimleri ile Uzaktan Güncelleme
Live Activities'i sunucu tarafından APNs push bildirimleri ile güncelleyebilirsiniz. Bunun için yapılandırmada enablePushNotifications: true ayarını etkinleştirmeniz yeterli.
// Push token'ı al
const pushToken = await instance.getPushToken();
console.log('Push Token:', pushToken);
// Token değişikliklerini dinle
const subscription = instance.addPushTokenListener((event) => {
console.log('Yeni push token:', event.pushToken);
// Token'ı sunucunuza gönderin
});
// Dinlemeyi durdur
subscription.remove();
Kullanıcı Etkileşimlerini Yakalama
Widget'lardaki buton tıklamaları ve toggle etkileşimlerini uygulama tarafında dinlemek de mümkün. Bu sayede widget üzerinden yapılan aksiyonlara uygulama içinden tepki verebilirsiniz:
import { addUserInteractionListener } from 'expo-widgets';
const subscription = addUserInteractionListener((event) => {
console.log('Widget etkileşimi:', {
kaynak: event.source, // Widget tanımlayıcısı
hedef: event.target, // Etkileşilen öğe
zaman: event.timestamp, // Zaman damgası
tip: event.type // 'ExpoWidgetsUserInteraction'
});
});
// Temizlik
subscription.remove();
Pratik İpuçları ve Dikkat Edilmesi Gerekenler
Geliştirme Süreci
Burada dürüst olmak gerekirse, geliştirme deneyimi henüz mükemmel değil. Birkaç şeye hazırlıklı olun:
- Widget'lar için hot reload yoktur. Her değişiklikte
npx expo prebuild -p ios --cleankomutuyla yeniden build almanız gerekiyor. Evet, biraz can sıkıcı ama şimdilik böyle. - Daha hızlı build'ler için geliştirme sırasında blank prebuild şablonu kullanmanız önerilir.
expo-widgetsşu anda alpha aşamasında — yani API'de kırılma değişiklikleri olabilir. Prodüksiyona almadan önce bunu göz önünde bulundurun.
Sınırlamalar
Her teknolojinin sınırları var, expo-widgets de bundan muaf değil:
- Yalnızca iOS: Android desteği henüz bulunmuyor.
- Expo Go desteklenmiyor: Development build şart.
- Widget
namealanı geçerli bir Swift tanımlayıcısı olmalı (boşluk veya özel karakter kullanmayın). - Widget'lar web isteği yapamaz — dolayısıyla web üzerinden barındırılan görselleri doğrudan yükleyemez.
- Live Activities en fazla 12 saat sürebilir.
@expo/uikütüphanesi şu anda widget'larda görsel desteğini tam olarak sunmuyor.
Ne Zaman Widget, Ne Zaman Live Activity?
Bu soruyu çok duyuyorum, o yüzden kısa bir rehber:
- Widget'lar şunlar için ideal: Takvim etkinlikleri, hava durumu, görev sayıları, alışkanlık takibi — yani periyodik olarak kontrol edilen bilgiler.
- Live Activities şunlar için ideal: Teslimat takibi, canlı skor, uçuş durumu, araç çağırma konumu — yani başlangıcı ve sonu belli olan devam eden süreçler.
Tam Proje Yapısı
Widget'lar ve Live Activities içeren tipik bir Expo proje yapısı şöyle görünüyor:
my-app/
├── app/
│ ├── index.tsx # Ana uygulama ekranı
│ └── _layout.tsx # Düzen bileşeni
├── widgets/
│ ├── SayacWidget.tsx # Sayaç widget bileşeni
│ └── HavaDurumuWidget.tsx # Hava durumu widget bileşeni
├── activities/
│ └── TeslimatActivity.tsx # Teslimat live activity
├── app.json # Expo yapılandırması
├── package.json
└── tsconfig.json
Sıkça Sorulan Sorular
expo-widgets ile Swift kodu yazmam gerekiyor mu?
Hayır, gerekmez. expo-widgets'ın en büyük avantajı tam da bu: React bileşenleri ile widget oluşturabiliyorsunuz. Arka planda @expo/ui bileşenleri doğrudan SwiftUI primitiflerine eşleniyor ama siz yalnızca TypeScript/JSX kodunuzla ilgileniyorsunuz. Config plugin prebuild sırasında tüm native dosyaları otomatik oluşturuyor.
expo-widgets Android'de çalışır mı?
Şu anda sadece iOS destekleniyor. Android'de widget oluşturmak isterseniz react-native-android-widget gibi üçüncü parti kütüphanelere bakabilirsiniz. Expo ekibinin gelecek sürümlerde Android desteği eklemeyi planladığı biliniyor, ama kesin bir tarih yok.
Widget'lar ne sıklıkla güncellenir?
Bu biraz karışık bir konu açıkçası. Widget güncellemeleri doğrudan sizin kontrolünüzde değil. iOS'a bir zaman çizelgesi sağlıyorsunuz ve WidgetKit pil ile performans optimizasyonlarına göre güncelleme zamanlamasına kendisi karar veriyor. updateSnapshot ile anlık güncelleme isteyebilir, updateTimeline ile önceden planlanmış güncellemeler ayarlayabilirsiniz.
Expo Go ile widget test edebilir miyim?
Maalesef hayır. Widget'lar native bir uzantı gerektirdiğinden Expo Go ile test edilemiyor. Geliştirme ve test sürecinde development build oluşturmanız gerekiyor. npx expo prebuild -p ios --clean komutuyla Xcode projesini oluşturup ardından simülatörde veya fiziksel cihazda test edebilirsiniz.
Live Activities ile widget arasındaki fark nedir?
Widget'lar ana ekranda veya kilit ekranında durağan bilgi gösteren kalıcı bileşenler. Hava durumu, görev listesi veya takvim gibi periyodik olarak kontrol edilen bilgiler için uygundur. Live Activities ise belirli bir süreci gerçek zamanlı takip etmek için var: teslimat, canlı maç skoru veya uçuş durumu gibi başlangıcı ve sonu belli olaylar. Live Activities Dynamic Island ve kilit ekranında gösteriliyor ve (daha önce de belirttiğim gibi) en fazla 12 saat sürebilir.