갑자기 변한 상황으로 기업협업 대신에 위코드에 남아 개인공부를 하게되었고,
React Native를 배우기 시작하면서 동기의 추천으로 Spotify 클론을 3주간 진행했다..
- 이미지 클릭시 영상으로 이동 -
<NavigatorName.Navigator>
<NavigatorName.Screen name="지정명" component={불러올컴포넌트명(스크린)} />
<NavigatorName.Screen name="지정명" component={불러올컴포넌트명(스크린)} />
</NavigatorName.Navigator>
위와 같은 형식으로 만들고 최상단(Base)의 경우 <NavigationContainer>
로 아래와 같이 감싸주어야 한다. 나의 경우 BottomTab Navigator가 최상단이기 때문에 아래와 같이 작성하고, 불러오는 컴포넌트가 또하나의 위와같은 형식으로 작성된 Stack Navigator이고 그것을 불러오는 식으로 Nesting을 해주었다.
const Tab = createBottomTabNavigator();
export default function Tabs() {
return (
<NavigationContainer>
<Tab.Navigator
initialRouteName="Home"
tabBar={props => <MyTabBar {...props} />}
>
<Tab.Screen
name="Home"
component={HomeStack}
listeners={({ navigation }) => ({
tabPress: e => {
navigation.navigate(HomeRoot);
},
})}
/>
<Tab.Screen name="Search" component={SearchStack} />
<Tab.Screen name="Library" component={LibraryTopTab} />
<Tab.Screen name="Premium" component={PremiumRoot} />
</Tab.Navigator>
</NavigationContainer>
);
};
export default function App() {
return (
<Provider store={store}>
<SafeAreaView style={styles.container}>
<StatusBar barStyle="light-content" />
<Tabs />
</SafeAreaView>
</Provider>
);
}
const goToListDetail = (id, data) => {
navigation.navigate('ListDetail', { id: id, data: data });
};
export default function ListDetail({ navigation, route }) {
const { id, data } = route.params;
//생략
route
를 props로 받아온 후에 route.params.id
나 위와같은 비구조화 할당 후 사용할 수 있다. HomeRoot 스크린에서 플레이리스트를 눌러 상세 스크린을 띄울때 route.params를 사용했다.위와 같은 상황을 제외한, 예를들어 나의 경우 여러겹 쌓인 Stack screen에서 선택한 음악을 최상단 Tabs 컴포넌트에 위치한 음악 플레이어 부분에서 불러와야하는 경우 (대체적으로 여러 곳에서 사용되어야 하는 state나 거리가 먼 하위 컴포넌트에서 상위 컴포넌트에 쓰일 state를 업데이트해야하는 경우 Redux를 통한 전역 상태 관리로 처리했다.)
import { spotify } from './config/server';
export const TOKEN_REQUEST_API = 'https://accounts.spotify.com/api/token';
export const TOKEN_AUTH = `Basic ${base64.encode(
spotify.ClientId + ':' + spotify.ClientSecret
)}`;
const BASE_URL = 'https://api.spotify.com/v1/';
export const instance = axios.create({
baseURL: BASE_URL,
});
export const SEARCH_URL = 'search?query=';
export const CATEGORY_URL = 'browse/categories?country=KR';
useEffect(() => {
getToken();
}, []);
useEffect(() => {
token.length !== 0 && getCategory();
}, [token]);
const getToken = async () => {
await axios(TOKEN_REQUEST_API, {
headers: {
Authorization: TOKEN_AUTH,
},
data: 'grant_type=client_credentials',
method: 'POST',
})
.then(tokenRes => {
dispatch(fetchToken(tokenRes.data.access_token));
instance.defaults.headers.common[
'Authorization'
] = `Bearer ${tokenRes.data.access_token}`;
})
.catch(err => {
console.log('token err', err);
});
};
const getCategory = async () => {
const res = await instance.get(CATEGORY_URL);
setCategories(res.data.categories.items);
await getPlaylistsData(res.data.categories.items);
};
const getPlaylist = async id => {
const res = await instance
.get(`browse/categories/${id}/playlists?country=KR`)
.catch(err => null);
return res;
};
const getPlaylistsData = async categories => {
const playlistRequest = categories?.map(category =>
getPlaylist(category.id)
);
await axios.all(playlistRequest).then(
axios.spread((...responses) => {
for (let i = 0; i < responses.length; i++) {
setPlayLists(prevPlayList => {
return {
...prevPlayList,
[categories[i].id]: responses[i].data.playlists.items,
};
});
}
})
);
};
플레이리스트 데이터를 요청하기 위해서는 카테고리 정보를 먼저 불러온뒤 그 카테고리의 각 id를 통해 그 카테고리에 속한 플레이리스트를 불러올 수 있게 되어있어서 getPlaylistData 함수를 통해 불러온 카테고리를 요청 배열로 바꾼뒤 axios.all을 통해 모든 요청을 한꺼번에 받아올 수 있게 처리하였다.
Spotify Api를 통해 정상적으로 카테고리와 플레이리스트 데이터를 불러오게 된 뒤에는 처음의 최적화가 문제였다. 애초에 토큰요청 => 카테고리 데이터 요청 => 각 카테고리의 모든 플레이리스트 데이터를 순서대로 요청해야 했고 또 생각보다 많은 정보를 받아 왔기 때문에 스플래시 스크린이 4초로 되어있어도 카테고리명만 불려온 화면이 나를 반겼었다.
ScrollView의 경우 보이지 않는 곳의 정보도 한번에 다 불러오는데 그 부분에서 아래의 다른부분도 렌더링을 진행 하느라 더 느리게 뜨고 바둑판식으로 랜덤하게 플레이리스트 커버 이미지가 떴었다. 최적화를 위해 ScrollView로 작성되어 있던 부분도 FlatList로 변경하고 initialNumToRender={2}
과 maxToRenderPerBatch={4}
같은 옵션을 주어서 위에 보이는 이미지와 같이 로딩 이후 보이는 부분의 렌더링이 잘 되어잇는 것을 확인할 수 있다.
onEndReached
props를 통해 잘라서 불러오게 만들었었는데, ListDetail에서 플레이리스트에 속한 음악의 제목과 가수명을 불러와서 보여주는 부분이 있고 랜덤재생 버튼의 동작을 위해 ListDetail에서 트랙 데이터를 불러오고 SongList로 route.params를 통해 전달해 주었다. 그래서 정보가 이미 다 있는 상태에서 무한 스크롤과 비슷하게 처리만 해주었다.(작성하면서 생각해보니 랜덤재생 버튼은 redux thunk를 사용해서 처리하고 ListDetail에서 정보를 잘라서 받아오고 SongList에서 onEndReached 마다 요청을 보내는 무한스크롤을 하면...?)
useEffect(() => {
if (sound) sound.unloadAsync(); // unload
playSound();
}, [currentIndex]);
// 인덱스가 변할때 sound를 unload 해줘야 다음곡 이전곡 변경을 했을때 이전곡이 멈추고 제대로 다음선택곡이 재생된다. 이 작업을 안해주면 첫곡 다음부터는 소리가 겹쳐서 재생된다.
useEffect(() => {
playController();
}, [playButton]);
// 리덕스로 관리되는 playButton 상태값을 통해서 bottomTab 뿐만 아닌 노래 디테일 모달에서도 일시정지 및 재생 관리가 가능해진다.
const playerStatus = async status => {
if (!status.isLoaded) {
if (status.error) {
console.log(`Error on expo AV: ${status.error}`);
}
} else {
dispatch(setStatus(status.isPlaying));
// 이 dispatch로 상태값을 업데이트 함으로써 현재 재생 상태를 여러곳에서 확인할수있다.
setDuration(status.durationMillis);
setPosition(status.positionMillis);
if (status.didJustFinish) {
dispatch(setNextMusic());
// status에서 제공되는 didJustFinish boolean 값을 통해 자동으로 현재곡이 끝날시 다음곡이 재생되게 설정할 수 있다.
}
}
};
const playSound = async () => {
const { sound } = await Audio.Sound.createAsync(
{ uri: currentMusic?.track?.preview_url },
{ shouldPlay: true },
// 이 곳에서 sound를 생성하고 shouldPlay 를 true로 줌으로써 노래를 선택해서 보내주자마자 자동으로 재생상태로 시작된다.
playerStatus
);
setSound(sound);
};
const playController = async () => {
if (!sound) {
return;
}
isPlaying ? await sound.pauseAsync() : await sound.playAsync();
};
// 이 함수에서 노래의 재생 상태를 변경 가능하다.
const getProgress = () => {
if (sound === null || duration === null || position === null) return 0;
let barStatus = Math.floor((position / duration) * 100);
dispatch(setBarStatus(barStatus, position, duration));
return barStatus;
};
// 이 함수에서 width 값을 구해서 지정해 줌으로 현재 재생위치를 표현할수 있다.
Home Tab과 음악재생 그리고 Redux의 사용이 이번 프로젝트의 목표 였기 때문에 거의 저렇게만 구현하는데에 프로젝트 기간의 반정도를 소요한것 같다. 개인 프로젝트 였던 만큼 문제를 직면하면 일단 구글 검색에 공식 문서 살펴보기로 시간도 많이 보냈다. (같이 남아 개인 프로젝트 진행하면서 많은 도움을 주신 라이현님 감사합니다..)
막상 프로젝트를 진행하는 동안 막막하기도 했지만 이번 개인 프로젝트로 조금 더 혼자 해결할 수 있다는 자신감이 더 생긴것같다.
나머지 부분은 후에 작성하기로..
리액트네이티브로 spotify api 를 이용한 안드로이드 앱을 만들려하고있습니다.. api 연동을하기위해서는 react-native-spotify-remote를 사용하는 건가요? 아니면 spotify developer 에있는 공식문서를 따라 해야하나요..
The musical whirlwind of spotify premium apk is like a bright lighthouse in the world of modern music.
와 리네까지.. 넘잘하세요👍👍