Merhaba,

 

Bu yazıda React Native ve Movie API ile Film Uygulaması yapımından bahsedeceğim. Uygulamada filmlerin listelendiği bir ana ekranımız ve film detay ekranımız olacak.

 

İlk olarak bir React Native projesi oluşturuyoruz:

react-native init MovieApp

 

Uygulamada iki adet ekranımız olacak ve bunlar arasında geçiş yapacağız. Bu yüzden React Navigation kullanacağız. Bununla ilgili gerekli paketleri kuruyoruz:

npm i @react-navigation/native @react-navigation/stack react-native-gesture-handler react-native-screens react-native-safe-area-context

Not: Eğer bu paketlerin kurulumu sırasında @babel/preset-env hatası alırsanız öncelikle şu kurulumu yapmanız gerekiyor:

npm i --save-dev @babel/preset-env

 

Şimdi projemizin iskeletini oluşturalım. Root dizininde components ve screens klasörleri oluşturuyoruz. components içerisine Header.js, Slider.js ve Footer.js dosyaları; screens içerisine ise HomeScreen.js ve DetailScreen.js dosyaları oluşturuyoruz. Yine root dizininde Navigation.js dosyası oluşturup şu şekilde bir ayarlama yapıyoruz:

Navigation.js:

import React from 'react'
import { createStackNavigator } from '@react-navigation/stack'
import { NavigationContainer } from '@react-navigation/native'

import HomeScreen from './screens/HomeScreen'
import DetailScreen from './screens/DetailScreen'

const Stack = createStackNavigator()

const screenOptions = {
  headerShown: false
}

const Navigation = () => (
  <NavigationContainer>
    <Stack.Navigator
      initialRouteName="HomeScreen"
      screenOptions={screenOptions}
    >
      <Stack.Screen name="HomeScreen" component={HomeScreen} />
      <Stack.Screen name="DetailScreen" component={DetailScreen} />
    </Stack.Navigator>
  </NavigationContainer>
)

export default Navigation
  • Stack Navigator oluşturduk ve üzerinde bulunacak ekranları belirttik.
  • Başlangıçta HomeScreen'in açılacağını belirttik.

 

App.js dosyasını açarak şu şekilde bir ayarlama yapıyoruz:

import React from 'react'

import Navigation from './Navigation'

const App = () => {
  return <Navigation />
}

export default App

 

Artık projemizin iskeleti hazır. Şimdi componentleri ve ekranları tasarlamaya, sonrasında da Movie API ile etkileşime sokmaya geçebiliriz.

 

Header.js:

import { View, Text, StyleSheet } from 'react-native'
import React from 'react'

const Header = () => {
  return (
    <View style={styles.container}>
      <Text style={styles.text}>Movie App</Text>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    height: 100,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#34495e'
  },
  text: {
    fontSize: 30,
    color: '#ffffff'
  }
})

export default Header

 

Footer.js:

import { View, Text, StyleSheet } from 'react-native'
import React from 'react'

const Footer = () => {
  return (
    <View style={styles.container}>
      <Text style={styles.text}>Movie App @ 2022</Text>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    height:50,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#34495e'
  },
  text: {
    fontSize: 16,
    color: '#ffffff'
  }
})

export default Footer

 

Slider.js:

import { View, Text, FlatList, Image, StyleSheet, TouchableOpacity } from 'react-native'
import React from 'react'

const Slider = ({ title, data, navigation }) => {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>{ title }</Text>
        <FlatList 
          horizontal={true} 
          showsHorizontalScrollIndicator={false}
          data={data} 
          renderItem={({ item, index }) => (
            <TouchableOpacity 
              style={styles.itemContainer}
              onPress={() => navigation.navigate('DetailScreen', {
                id: item.id
              })}
            >
              <View style={styles.itemTopInfos}>
                <Text style={styles.itemRate}>{ item.vote_average }</Text>
                <Text style={styles.itemYear}>{ item.release_date.substr(0, 4) }</Text>
              </View>              
              <Image 
                style={styles.itemImage} 
                source={{ uri: `https://www.themoviedb.org/t/p/w300_and_h450_bestv2${item.poster_path}` }} 
              />
              <Text style={styles.itemTitle}>{ item.title }</Text>
            </TouchableOpacity>            
          )}
          keyExtractor={item => item.id}
        />
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    margin: 10
  },
  title: {
    fontSize: 20,
    color: '#000000',
    marginBottom: 10
  },
  itemContainer: {
    marginRight: 10,
    width: 150
  },
  itemTopInfos: {
    flexDirection: 'row',
    justifyContent: 'space-between'
  },
  itemRate: {
    backgroundColor: '#27ae60',
    color: '#ffffff',
    padding: 3,
    borderTopLeftRadius: 3,
    borderTopRightRadius: 3
  },
  itemYear: {
    backgroundColor: '#34495e',
    color: '#ffffff',
    padding: 3,
    borderTopLeftRadius: 3,
    borderTopRightRadius: 3
  },
  itemImage: {
    height: 225
  },
  itemTitle: {
    fontWeight: 'bold',
    color: '#000000'
  }
})

export default Slider
  • title, data ve navigation verilerini props olarak aldık. Çünkü bu component'i HomeScreen'de hem popüler hem de trend filmler için kullanacağız.
  • Verileri yatay şekilde bir FlatList üzerinde göstereceğiz.
  • Her bir filme tıklandığında navigation ile detay ekranını açtıracağız. Filmin id'sini de detay ekranına parametre olarak göndereceğiz.

 

Tabi unutmadan projemizin root dizininde bir env.js dosyası açıp API bilgilerini yazıyoruz:

export const API_URL = "https://api.themoviedb.org/3"
export const API_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiI2NWNkMjAwZDFkNWMyOTE1NGIwNTk0YjZkZTllYTA3MSIsInN1YiI6IjVhYTdkYjcyOTI1MTQxNWUzOTAxZGUyMiIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.n4H4Ag0mQxg-530d1wFbfUkd_OIiPuUP59OwCDQpH6A"

 

HomeScreen.js:

import { View, ScrollView, StyleSheet, ActivityIndicator } from 'react-native'
import React, { useState, useEffect } from 'react'

import Header from '../components/Header'
import Slider from '../components/Slider'
import Footer from '../components/Footer'

import { API_URL, API_TOKEN } from "../env"

const HomeScreen = ({ navigation }) => {
  const [loadingPopularMovies, setLoadingPopularMovies] = useState(true)
  const [loadingTrendMovies, setLoadingTrendMovies] = useState(true)
  const [popularMoviesData, setPopularMoviesData] = useState([])
  const [trendMoviesData, setTrendMoviesData] = useState([])

  const getPopularMovies = async () => {
    try {
      const response = await fetch(API_URL + '/movie/popular', {
        headers: {
          'Authorization': 'Bearer ' + API_TOKEN
        }
      })
      const json = await response.json()
      setPopularMoviesData(json.results)
    } catch (error) {
      console.error(error)
    } finally {
      setLoadingPopularMovies(false)
    }
  }

  const getTrendMovies = async () => {
    try {
      const response = await fetch(API_URL + '/movie/top_rated', {
        headers: {
          'Authorization': 'Bearer ' + API_TOKEN
        }
      })
      const json = await response.json()
      setTrendMoviesData(json.results)
    } catch (error) {
      console.error(error)
    } finally {
      setLoadingTrendMovies(false)
    }
  }

  useEffect(() => {
    getPopularMovies()
    getTrendMovies()
  }, [])

  return (
    <View style={styles.container}>
      <Header />
      <ScrollView>
        {loadingPopularMovies ? <ActivityIndicator/> : (
          <Slider title="Popular Movies" data={popularMoviesData} navigation={navigation} />
        )}
        {loadingTrendMovies ? <ActivityIndicator/> : (
          <Slider title="Trend Movies" data={trendMoviesData} navigation={navigation} />
        )}
      </ScrollView>      
      <Footer />
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1
  }
})

export default HomeScreen
  • useState kullanarak 4 tane değişken oluşturduk. loadingPopularMovies ve loadingTrendMovies değişkenleri ile popüler ve trend filmler API'dan gelene kadar ekranda loading işareti göstereceğiz. popularMoviesData ve trendMoviesData değişkenlerinde ise API'dan aldığımız film verilerini tutacağız.
  • getPopularMovies ve getTrendMovies fonksiyonlarını tanımlayarak API'a çağrı yaptık. Bu çağrılar sırasında header kısmında API_TOKEN'ı da gönderdik. Gelen verilerin içindeki results dizisini set fonksiyonlarıyla state'de bulunan değişkenlerimize aktardık.
  • useEffect ile ekran render edildikten sonra (componentDidMount gibi) getPopularMovies ve getTrendMovies fonksiyonlarının çağrılmasını sağladık.
  • Slider component'ine title, data ve navigation verilerini gönderdik.

 

import { View, Text, ScrollView, ActivityIndicator, Image, FlatList, StyleSheet } from 'react-native'
import React, { useState, useEffect } from 'react'

import Header from '../components/Header'
import Footer from '../components/Footer'

import { API_URL, API_TOKEN } from '../env'

const DetailScreen = ({ route }) => {
  const [loadingMovieDetail, setLoadingMovieDetail] = useState(true)
  const [loadingCast, setLoadingCast] = useState(true)
  const [movieDetailData, setMovieDetailData] = useState({})
  const [castData, setCastData] = useState([])

  const { id } = route.params

  const getMovieDetail = async () => {
    try {
      const response = await fetch(API_URL + '/movie/' + id, {
        headers: {
          'Authorization': 'Bearer ' + API_TOKEN
        }
      })
      
      const json = await response.json()
      setMovieDetailData(json)
    } catch (error) {
      console.error(error)
    } finally {
      setLoadingMovieDetail(false)
    }
  }

  const getCast = async () => {
    try {
      const response = await fetch(API_URL + '/movie/' + id + '/credits', {
        headers: {
          'Authorization': 'Bearer ' + API_TOKEN
        }
      })
      const json = await response.json()
      setCastData(json.cast)
    } catch (error) {
      console.error(error)
    } finally {
      setLoadingCast(false)
    }
  }

  useEffect(() => {
    getMovieDetail()
    getCast()
  }, [])

  return (
    <View style={styles.container}>
      <Header />
      {loadingMovieDetail && loadingCast ? <ActivityIndicator/> : (
        <ScrollView style={styles.scrollContainer}>
          <View style={styles.topInfos}>
            <Image 
              style={styles.image}
              source={{ uri: `https://www.themoviedb.org/t/p/w300_and_h450_bestv2${movieDetailData.poster_path}` }} 
            />
            <View style={styles.rightInfos}>
              <Text style={styles.title}>{ movieDetailData.title }</Text>
              <Text style={styles.rate}>{ movieDetailData.vote_average }</Text>
              <Text style={styles.year}>{ movieDetailData.release_date.substr(0, 4) }</Text>
              <FlatList
                horizontal={true}
                showsHorizontalScrollIndicator={false}
                style={styles.genreList}
                data={movieDetailData.genres}
                renderItem={({ item, index }) => (
                  <Text style={styles.genreItem}>{ item.name }</Text>
                )}
                keyExtractor={item => item.id}
              />
            </View>          
          </View>        
          <Text style={styles.description}>{ movieDetailData.overview }</Text>
          <Text style={styles.castTitle}>Cast</Text>
          <FlatList
            horizontal={true}
            showsHorizontalScrollIndicator={false}
            style={styles.castList}
            data={castData}
            renderItem={({ item, index }) => (
              <View style={styles.castItem}>
                <Image 
                  style={styles.castItemImage}
                  source={{ uri: item.profile_path !== null ? `https://www.themoviedb.org/t/p/w300_and_h450_bestv2${item.profile_path}` : `https://via.placeholder.com/100x150` }} 
                />
                <Text style={styles.castItemName}>{ item.name }</Text>
                <Text style={styles.castItemTitle}>{ item.character }</Text>
              </View>            
            )}
            keyExtractor={item => item.id}
          />
        </ScrollView> 
      )}     
      <Footer />
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1
  },
  scrollContainer: {
    margin: 10
  },
  topInfos: {
    flexDirection: 'row'
  },
  image: {
    height: 225,
    width: 150
  },
  rightInfos: {
    flex: 1,
    marginLeft: 10
  },
  title: {
    fontWeight: 'bold',
    fontSize: 16,
    color: '#000000'
  },
  rate: {
    backgroundColor: '#27ae60',
    color: '#ffffff',
    padding: 3,
    borderTopLeftRadius: 3,
    borderTopRightRadius: 3,
    alignSelf: 'flex-start',
    marginTop: 10,
    marginBottom: 10
  },
  year: {
    backgroundColor: '#34495e',
    color: '#ffffff',
    padding: 3,
    borderTopLeftRadius: 3,
    borderTopRightRadius: 3,
    alignSelf: 'flex-start',
    marginBottom: 10
  },
  genreList: {
    flexShrink: 1
  },
  genreItem: {
    backgroundColor: '#34495e',
    color: '#ffffff',
    padding: 3,
    borderRadius: 3,
    alignSelf: 'flex-start',
    marginRight: 5,
    fontSize: 12
  },
  description: {
    color: '#000000',
    marginTop: 10
  },
  castTitle: {
    marginTop: 10,
    fontWeight: 'bold',
    color: '#000000',
    fontSize: 15
  },
  castList: {
    marginTop: 10
  },
  castItem: {
    width: 100
  },
  castItemImage: {
    height: 120,
    width: 80
  },
  castItemName: {
    color: '#000000',
    fontSize: 13
  },
  castItemTitle: {
    fontSize: 12
  }
})

export default DetailScreen
  • Ekrana gönderilen id parametresini route props'u üzerinden aldık.
  • useState kullanarak 4 tane değişken oluşturduk. loadingMovieDetail ve loadingCast değişkenleri ile film detayı ve oyuncu kadrosu API'dan gelene kadar ekranda loading işareti göstereceğiz. movieDetailData ve castData değişkenlerinde ise API'dan aldığımız verileri tutacağız.
  • getMovieDetail ve getCast fonksiyonlarını tanımlayarak API'a çağrı yaptık. Bu çağrılar sırasında header kısmında API_TOKEN'ı da gönderdik. Gelen verilerin içerisinden ihtiyacımız olanları set fonksiyonlarıyla state'de bulunan değişkenlerimize aktardık.
  • useEffect ile ekran render edildikten sonra (componentDidMount gibi) getMovieDetail ve getCast fonksiyonlarının çağrılmasını sağladık.

 

Artık uygulamamızı test edebiliriz:

react-native run-android

 

Uygulamamızın ekran görüntüleri şu şekilde olacaktır:

Projenin kaynak kodlarına buradan ulaşabilirsiniz.

 

Umarım yararlı olmuştur.

 

İyi çalışmalar.