React Native ve Hooks ile Haber Uygulaması Yapımı – Bölüm 1
Önceki yazılarımızda React Native’in genel olarak mimarisinden ve kurulumundan bahsetmiştik. React Hooks sayesinde de fonksiyonel bileşenlerde state yönetiminin nasıl yapıldığına değinmiştik. Şimdi gelin bu bilgileri kullanarak basit bir haber uygulaması yapalım.
Projenin oluşturulması
ReactNews adında projemizi oluşturmak için React Native CLI'ını kullanabiliriz:
react-native init ReactNews
Proje dizinine giderek:
cd ReactNews
react-native run-ios
komutunu çalıştırdığınızda aşağıdaki gibi bir ekran sizi karşılayacaktır:
Projenin VSCode Üzerinde Geliştirimi
Geliştirim için VSCode oldukça başarılı bir editör. VSCode’daki React Native Tools eklentisi sayesinde geliştirim süreci otomatize edilmiş ve sizi terminale sürüklemeden işinizi halletmenizi sağlıyor.
Bu eklentiyi yükledikten sonra VSCode’da üstteki File menüsünden Open Folder..
ile projenin bulunduğu dizini açıp, üstte yer alan Debug
menüsünden Start Debugging
'i seçtiğinizde uygulamanın çalışmadığını göreceksiniz. Bunun sebebi, VSCode’un ihtiyaç duyduğu yapılandırma dosyasının (launch.json
) proje dizininizde bulunmamasıdır. Bunun için sol üstte Debug
bölmesinde No Configurations
'a tıkladığınızda Add Configuration
kısmına giderek “launch.json” dosyasını otomatik olarak oluşturabilirsiniz:
Hemen sağ altta yer alan Add Configuration...
butonu ise otomatik olarak istediğiniz platform için yapılandırma kodunu ekleyecektir. Eğer iOS üzerinde geliştirim yapıyorsanız {}React Native: Debug iOS
'i seçebilirsiniz. Bu seçim işlemi sonrasında otomatik olarak kod eklendikten sonra `launch.json dosyasını kaydedebilirsiniz:
Not: Launch Program
isimli yapılandırmaya ihtiyacımız yok. Bu nedenle o bloğu silebilirsiniz.
launch.json’ın son hali aşağıdaki gibi olmalıdır:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug iOS",
"cwd": "${workspaceFolder}",
"type": "reactnative",
"request": "launch",
"platform": "ios"
}
]
}
Sonrasında Debug kısmında “No Configurations”‘ın yanındaki yeşil play butonuna bastığınızda, otomatik “Debug iOS” seçeneği gelecek ve proje iOS simülatörü üzerinde çalışıyor olacaktır.
Şimdi App.js
dosyası içerisine gelelim ve kodun tamamını silip yerine aşağıdaki kodu yapıştıralım:
import React from 'react';
import { View, Text } from 'react-native';
const App = () => {
return (
<View style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center'
}}>
<Text>Hello World</Text>
</View>
);
};
export default App;
Not: Kodu kaydettikten sonra simülatöre gelip iOS için CMD + R, Android için R + R'ye bastığınızda otomatik olarak kodu cihaza atıp çalıştıracaktır.
Kodun yaptığı iş aslında oldukça basit. Bir View elemanını içerisinde düşey ve yatay olarak ortalanan Hello World
metninin görüntülenmesini sağlıyor. style
içerisindeki kısımları inceleyecek olursak:
- flex: 1
<View>
elemanının ekranın tamamını kaplamasını sağlıyor - flexDirection: ‘column' Alt elemanların akış yönünü belirliyor. Varsayılan:
colum
(düşey) - justifyContent: ‘center'
flexDirection
'a (düşeye) göre alt elemanların nasıl dağılacağını belirliyor. Varsayılan:flex-start
- alignItems: ‘center'
flexDirection
'ın zıt yönüne (yataya) göre nasıl dağılacağını belirliyor. Varsayılan:stretch
Not: Daha birçok stil özelliği bulunmakta. Fakat basit olması açısından şimdilik bu kısımların tamamına değinmeyeceğiz. Flexbox
ile ilgili ayrıntılı bilgi için https://facebook.github.io/react-native/docs/flexbox adresini ziyaret edebilirsiniz.
Haber Konularının Listelenmesi
Bu ekranda (App.js) haber konularının listeleyeceğiz. Listeleme işlemlerinde <ScrollView>
veya <FlatList>
kullanılabilir. Fakat burada FlatList’in öne geçtiği bir konu var: lazy-loading
. Lazy loading (gecikmeli yükleme), liste elemanlarının ekranda sadece görüntülenen kısmının yüklenmesini sağlar. Örneğin 1 milyon adet liste elemanı varsa ekranda görüntülenen 20-30 adet liste elemanı belleğe yüklenecektir. Bu sayede tüketim düşürülerek, uygulamanın yüksek performanslı olarak çalışması sağlanır. Bu projede de liste elemanlarının görüntülenmesi için <FlatList>
bileşenini kullanabiliriz. Şimdi FlatList’i import edelim ve “Hello World” yazan Text elemanını kaldırıp aşağıdaki gibi FlatList elemanını ekleyelim:
import React from 'react';
import { View, Text, FlatList } from 'react-native';
const App = () => {
return (
<View style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center'
}}>
<FlatList
data={[{ key: 'a' }, { key: 'b' }, { key: 'c' }, { key: 'd' }]}
renderItem={({ item }) => <Text>{item.key}</Text>}
/>
</View>
);
};
export default App;
FlatList elemanında şimdilik data ve renderItem özellikleri bulunuyor:
- data: Listedeki elemanları barındıran dizidir. data dizisinde
key
değişkeninin olması önemlidir. Çünkü React, bu key değişkenine göre listedeki elemanları ve arayüzü günceller. Aksi halde"missing keys for items"
uyarısı verecektir. - renderItem: Listedeki tek bir elemanın nasıl görüntüleneceğini belirten fonksiyondur. Burada data dizisindeki her bir eleman için
<Text>
bileşeninin görüntüleneceğini belirtilmiştir.
App.js dosyasını kaydedip simülatörde çalıştırdığımızda a
ve b
elemanlarının iPhone X simülatöründe çentik arkasına StatusBar’a gizlendiğini görebilirsiniz:
Bunu engellemek için View yerine <SafeAreaView>
bileşenini kullanarak, elemanların StatusBar veya NavigationBar gibi native bileşenler üzerine yazması engellenebilir:
import React from 'react';
import { SafeAreaView, Text, FlatList } from 'react-native';
const App = () => {
return (
<SafeAreaView style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center'
}}>
<FlatList
data={[{ key: 'a' }, { key: 'b' }, { key: 'c' }, { key: 'd' }]}
renderItem={({ item }) => <Text>{item.key}</Text>}
/>
</SafeAreaView>
);
};
export default App;
Şimdi a, b, c, d gibi harfler yerine gerçek haber kategorilerini barındıracak dataSource
isimli diziyi oluşturalım:
import React from 'react';
import { SafeAreaView, Text, FlatList } from 'react-native';
const App = () => {
const dataSource = [
{ category: 'Teknoloji' },
{ category: 'Spor' },
{ category: 'Sağlık' },
{ category: 'Ekonomi' },
{ category: 'Eğitim' },
{ category: 'Müzik' },
{ category: 'Tiyatro' },
{ category: 'Sinema' },
{ category: 'Hava' },
{ category: 'Seyahat' },
{ category: 'Astroloji' },
{ category: 'Otomobil' },
{ category: 'Galeri' },
{ category: 'Video' },
];
return (
<SafeAreaView style={{
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center'
}}>
<FlatList
data={dataSource}
renderItem={({ item }) => <Text>{item.category}</Text>}
/>
</SafeAreaView>
);
};
export default App;
Diziyi listeledik fakat key özelliği bulunmadığı için “missing keys” uyarısı aldık. Bunun için elle ayrı ayrı key özelliğini dizi elemanlarına ekleyebiliriz. Fakat bize zaman kazandırması için, bu işlemi otomatize etmek adına FlatList’in keyExtractor
özelliğini kullanabiliriz:
- keyExtractor: Liste elemanlarına key’in nasıl aktarılacağını bildiren fonksiyondur. Bu fonksiyon ile herhangi bir özellik adı key olarak kullanılabilir.
Normalde key olarak id verilir fakat id’miz olmadığından dolayı ve dataSource dizisindeki category değişkeni de benzersiz olduğu için category’i key olarak kullanabiliriz. FlatList’in son hali aşağıdaki gibi olacaktır:
<FlatList
data={dataSource}
renderItem={({ item }) => <Text>{item.category}</Text>}
keyExtractor={( item ) => item.category }
/>
Listeleme işlemini gerçekleştirdik. Şimdi listenin tasarımını güzelleştirelim.
Liste Tasarımının Oluşturulması
Unsplash sitesinden ücretsiz bir şekilde istediğimiz görseli uygulamamıza ekleyebiliriz. Unsplash’teki görsel url’lerinin yapısı genellikle aşağıdaki gibidir:
- Unsplash base URL’i
- Görsel Id’si
- Görsel kalitesi ve boyutlandırma ile ilgili query parametreleri
Örnek verecek olursak aşağıdaki gibi bir görsel URL’i oluşturulabilir:
Bu url’i değişkenlere ayırmak istediğimizde aşağıdaki gibi bir kod oluşturabiliriz:
const imageBaseUrl = "https://images.unsplash.com/photo-";
const imageParameters = "?auto=format&fit=crop&w=375&q=80";
const imageId = '1478358161113-b0e11994a36b'
Tüm kategoriler için imageId’ler toplandığında, değişkenlerin son hali aşağıdaki gibi olacaktır:
const imageBaseUrl = "https://images.unsplash.com/photo-";
const imageParameters = "?auto=format&fit=crop&w=375&q=80";
const dataSource = [
{ category: 'Teknoloji', imageId: '1478358161113-b0e11994a36b' },
{ category: 'Spor', imageId: '1521412644187-c49fa049e84d' },
{ category: 'Sağlık', imageId: '1526256262350-7da7584cf5eb' },
{ category: 'Ekonomi', imageId: '1542222024-c39e2281f121' },
{ category: 'Eğitim', imageId: '1503676260728-1c00da094a0b' },
{ category: 'Müzik', imageId: '1511671782779-c97d3d27a1d4' },
{ category: 'Tiyatro', imageId: '1507924538820-ede94a04019d' },
{ category: 'Sinema', imageId: '1478720568477-152d9b164e26' },
{ category: 'Hava', imageId: '1530908295418-a12e326966ba' },
{ category: 'Seyahat', imageId: '1473625247510-8ceb1760943f' },
{ category: 'Astroloji', imageId: '1532968961962-8a0cb3a2d4f5' },
{ category: 'Otomobil', imageId: '1537041373298-55dbb337e651' },
{ category: 'Galeri', imageId: '1500051638674-ff996a0ec29e' },
{ category: 'Video', imageId: '1524253482453-3fed8d2fe12b' },
];
Kategori isimlerininin arkaplanına görsel atamak için <ImageBackground>
bileşenini kullanabiliriz. ImageBackground bileşeni temel olarak görsel Url’inin yer aldığı source
özelliğini ve görselin boyutlarının yer aldığı style özelliğini içerir. Bu bilgileri kullanarak FlatList bileşeninin renderItem fonksiyonunu bileşen içerisine taşıyarak düzenleyelim:
import React from 'react';
import { Text, FlatList, SafeAreaView, ImageBackground } from 'react-native';
const App = () => {
const imageBaseUrl = "https://images.unsplash.com/photo-";
const imageParameters = "?auto=format&fit=crop&w=375&q=80";
const dataSource = [
{ category: 'Teknoloji', imageId: '1478358161113-b0e11994a36b' },
{ category: 'Spor', imageId: '1521412644187-c49fa049e84d' },
{ category: 'Sağlık', imageId: '1526256262350-7da7584cf5eb' },
{ category: 'Ekonomi', imageId: '1542222024-c39e2281f121' },
{ category: 'Eğitim', imageId: '1503676260728-1c00da094a0b' },
{ category: 'Müzik', imageId: '1511671782779-c97d3d27a1d4' },
{ category: 'Tiyatro', imageId: '1507924538820-ede94a04019d' },
{ category: 'Sinema', imageId: '1478720568477-152d9b164e26' },
{ category: 'Hava', imageId: '1530908295418-a12e326966ba' },
{ category: 'Seyahat', imageId: '1473625247510-8ceb1760943f' },
{ category: 'Astroloji', imageId: '1532968961962-8a0cb3a2d4f5' },
{ category: 'Otomobil', imageId: '1537041373298-55dbb337e651' },
{ category: 'Galeri', imageId: '1500051638674-ff996a0ec29e' },
{ category: 'Video', imageId: '1524253482453-3fed8d2fe12b' },
];
renderItem = ({ item }) => {
return (
<ImageBackground
source={{ uri: imageBaseUrl + item.imageId + imageParameters }}
style={{
width: '100%',
height: 100,
justifyContent: 'center'
}}>
<Text style={{
textAlign: 'center',
color: '#fff',
fontSize: 15
}}>{item.category}</Text>
</ImageBackground>
);
}
return (
<SafeAreaView>
<FlatList
data={dataSource}
renderItem={this.renderItem}
keyExtractor={(item) => item.category}
/>
</SafeAreaView>
);
};
export default App;
Bu haliyle dahi önceki görselsiz kategori listesine göre daha güzel görünüyor. Fakat dataSource
içerisinde kategori listesi uzun olduğu için kullanıcı scroll etmek durumunda kalıyor. Bunun yerine ekrana sığması ve güzel gözükmesi açısından listeyi 3’lü grid olacak şekilde değiştirmemiz gerekli. FlatList bileşeni içerisinde numColumns
alanı tam olarak bu ihtiyacımızı karşılıyor ve değerini atadığımızda 3 kolonlu bir liste oluşturmamızı sağlıyor. FlatList bileşenini aşağıdaki şekilde değiştirebiliriz:
<FlatList
data={dataSource}
renderItem={this.renderItem}
keyExtractor={(item) => item.category}
numColumns={3}
/>
Kodu atıp çalıştırdığınızda diğer kategorilerin kaybolduğunu göreceksiniz. Bunun nedeni ImageBackground’un width özelliğine %100 verdiğimizden dolayı kaynaklanıyor. 3’lü kareler halinde listeleme yapacağımız için width özelliği, ekran genişliğinin 3’e bölünmüş hali olması gerekiyor. Kare olduğu için height da width’e eşit olmalı.
Ekranın mevcut genişliğini almak için React Native’in Dimensions Api
'sini kullanacağız. Bu API ile her bir kare (tile)’ın genişliği aşağıdaki gibi hesaplanabilir:
const numColumns = 3;
const tileWidth = Dimensions.get('window').width / numColumns;
Bu değişkenleri ekleyip koda uygulayalım:
import React from 'react';
import { Text, FlatList, SafeAreaView, ImageBackground, Dimensions } from 'react-native';
const App = () => {
const numColumns = 3;
const tileWidth = Dimensions.get('window').width / numColumns;
const imageBaseUrl = "https://images.unsplash.com/photo-";
const imageParameters = "?auto=format&fit=crop&w=375&q=80";
const dataSource = [
{ category: 'Teknoloji', imageId: '1478358161113-b0e11994a36b' },
{ category: 'Spor', imageId: '1521412644187-c49fa049e84d' },
{ category: 'Sağlık', imageId: '1526256262350-7da7584cf5eb' },
{ category: 'Ekonomi', imageId: '1542222024-c39e2281f121' },
{ category: 'Eğitim', imageId: '1503676260728-1c00da094a0b' },
{ category: 'Müzik', imageId: '1511671782779-c97d3d27a1d4' },
{ category: 'Tiyatro', imageId: '1507924538820-ede94a04019d' },
{ category: 'Sinema', imageId: '1478720568477-152d9b164e26' },
{ category: 'Hava', imageId: '1530908295418-a12e326966ba' },
{ category: 'Seyahat', imageId: '1473625247510-8ceb1760943f' },
{ category: 'Astroloji', imageId: '1532968961962-8a0cb3a2d4f5' },
{ category: 'Otomobil', imageId: '1537041373298-55dbb337e651' },
{ category: 'Galeri', imageId: '1500051638674-ff996a0ec29e' },
{ category: 'Video', imageId: '1524253482453-3fed8d2fe12b' },
];
renderItem = ({ item }) => {
return (
<ImageBackground
source={{ uri: imageBaseUrl + item.imageId + imageParameters }}
style={{
width: tileWidth,
height: tileWidth,
justifyContent: 'center',
}}>
<Text style={{
textAlign: 'center',
color: '#fff',
fontSize: 15
}}>{item.category}</Text>
</ImageBackground>
);
}
return (
<SafeAreaView>
<FlatList
data={dataSource}
renderItem={this.renderItem}
keyExtractor={(item) => item.category}
numColumns={numColumns}
/>
</SafeAreaView>
);
};
export default App;
Şimdi her bir kategorinin buton gibi tıklanabilir olmasını sağlayalım. Tıklandığında da ekrana kategorinin ismini <Alert>
ile bastıralım. Bunun için <TouchableHighlight>
bileşenini kullanabiliriz. Herhangi bir View elemanını TouchableHighlight ile sarmaladığınızda o view elemanı tıklanabilir hale gelecektir.Bizim örneğimizde de ImageBackground bileşeni için bu işlemi uygulamamız gerekiyor:
<TouchableHighlight onPress={() => { Alert.alert(item.category) }}>
{/* ImageBackground bileşeni */}
</TouchableHighlight>
Kodun son hali aşağıdaki gibi olacaktır:
import React from 'react';
import { Text, FlatList, SafeAreaView, ImageBackground, Dimensions, TouchableHighlight, Alert } from 'react-native';
const App = () => {
const numColumns = 3;
const tileWidth = Dimensions.get('window').width / numColumns;
const imageBaseUrl = "https://images.unsplash.com/photo-";
const imageParameters = "?auto=format&fit=crop&w=375&q=80";
const dataSource = [
{ category: 'Teknoloji', imageId: '1478358161113-b0e11994a36b' },
{ category: 'Spor', imageId: '1521412644187-c49fa049e84d' },
{ category: 'Sağlık', imageId: '1526256262350-7da7584cf5eb' },
{ category: 'Ekonomi', imageId: '1542222024-c39e2281f121' },
{ category: 'Eğitim', imageId: '1503676260728-1c00da094a0b' },
{ category: 'Müzik', imageId: '1511671782779-c97d3d27a1d4' },
{ category: 'Tiyatro', imageId: '1507924538820-ede94a04019d' },
{ category: 'Sinema', imageId: '1478720568477-152d9b164e26' },
{ category: 'Hava', imageId: '1530908295418-a12e326966ba' },
{ category: 'Seyahat', imageId: '1473625247510-8ceb1760943f' },
{ category: 'Astroloji', imageId: '1532968961962-8a0cb3a2d4f5' },
{ category: 'Otomobil', imageId: '1537041373298-55dbb337e651' },
{ category: 'Galeri', imageId: '1500051638674-ff996a0ec29e' },
{ category: 'Video', imageId: '1524253482453-3fed8d2fe12b' },
];
renderItem = ({ item }) => {
return (
<TouchableHighlight onPress={() => { Alert.alert(item.category) }}>
<ImageBackground
source={{ uri: imageBaseUrl + item.imageId + imageParameters }}
style={{
width: tileWidth,
height: tileWidth,
justifyContent: 'center'
}}>
<Text style={{
textAlign: 'center',
color: '#fff',
fontSize: 15
}}>{item.category}</Text>
</ImageBackground>
</TouchableHighlight>
);
}
return (
<SafeAreaView>
<FlatList
data={dataSource}
renderItem={this.renderItem}
keyExtractor={(item) => item.category}
numColumns={numColumns}
/>
</SafeAreaView>
);
};
export default App;
Haber kategorisine tıklandığında aşağıdaki gibi kategori adı alert ile ekrana basılmış olacaktır:
Sonuç Olarak
Bu yazımızda React Native projesinin VSCode’a nasıl entegre edileceğini, liste elemanlarının oluşturulmasını, grid yapısını ve TouchableHighlight ile tıklanabilir arayüz bileşenlerinin oluşturulmasını sağladık. Sonraki yazımızda kategoriye tıklandığında ilgili haber listesinin ağ üzerinden getirilmesi ve listelenmesi gibi konulara değineceğiz. Eğer bu proje hakkında sorularınız veya görüşleriniz varsa yorum kısmından bize yazabilirsiniz. Bir sonraki yazımızda görüşmek üzere.