EPITUNES
Epimac workshop, about 45 minutes.
You are going to build a small music search app with React Native, Expo and TypeScript. You type an artist name, the app calls the iTunes API and displays the songs with their cover. It runs on your own phone, iOS or Android.
Requirements: a computer with a web browser and a phone (iPhone or Android). No Mac needed and nothing to install on the computer. You can work alone or in a group of 2 to 4.
Working in a group
Each group uses one Snack, on one computer. Change who types at every step, so that everybody writes some code. Everyone in the group can scan the QR code of this Snack and run the app on their own phone.
At the end, the group sends one link in the form, with the names of all the members.
Project tree
EPITUNES/
README.mdx
starter/
App.tsx
package.json
tsconfig.json
api/
itunes.ts
components/
SongCard.tsx
data/
sampleSongs.ts
types/
song.ts
starter/ is the skeleton you have to complete:
App.tsxis the main screen, with the search bar and the list of songsapi/itunes.tscontains the call to the API (step 2)components/SongCard.tsxdisplays one song of the list (steps 1 and 3)data/sampleSongs.tscontains 5 hardcoded songs, used until your search workstypes/song.tscontains theSongtype, the shape of a song
Where to find the skeleton
You don't need to clone this repo. The files of starter/ are already loaded in this Snack:
https://snack.expo.dev/@yasminebnl/epitunes
Snack is the online editor of Expo. Open the link and work directly in it: your changes stay in your browser, the original is not modified.
In the code, everything you have to write is marked with a TODO and the number of the step.
Why Expo, React Native and TypeScript
Not everyone has a Mac, and you need one to build a native iOS app because Xcode only exists on macOS. With React Native you write the app once and it runs on both iOS and Android. Expo provides the tools around it. Today we use two of them: Snack, the online editor, and Expo Go, an app that runs your code on your phone.
The code is written in TypeScript. It is JavaScript with types: the editor knows the shape of your data and warns you when you use a field that doesn't exist.
During the workshop your app runs inside Expo Go. Publishing an app on the App Store or the Play Store is also possible with Expo, without a Mac, but you need developer accounts. It is explained in the Expo documentation (links at the end).
Timing
| Step | Time |
|---|---|
| 0. Setup | 5 min |
| 1. Display the songs | 10 min |
| 2. Call the API | 15 min |
| 3. Customize | 10 min |
| 4. Bonus: open the preview | if you have time |
| Send your link and show your app | 5 min |
TypeScript notes if you only know C
- Like in C, values have types, but the type comes after the name:
const name: string = 'toto';. Most of the time you can skip it, TypeScript guesses it. type Song = { ... }describes an object, like a struct. A?after a field name means that the field can be missing.- Strings are written with quotes and you concatenate them with
+. song.trackNamereads a field, like with a struct.- In the part of the code that looks like HTML,
{value}inserts the value of a variable.Viewis a container,Textdisplays text andImagedisplays an image.
When the editor underlines something in red, put your mouse on it. The message usually tells you what is wrong.
Step 0: setup
- Install Expo Go on your phone, or update it if you already have it. Expo Go asks you to log in: create a free Expo account, it takes a minute.
- Open the Snack link on the computer of the group.
- In Snack, in the panel on the right, select
My Device. A QR code shows up.
- iPhone
- Android
- No phone
Install Expo Go from the App Store. Open the Camera app and scan the QR code.
Install Expo Go from the Play Store. Open Expo Go and scan the QR code with its scanner.
Select the Web tab in the panel on the right of Snack. The app runs in the browser, so you can do steps 1 and 3. The search of step 2 and the preview of step 4 only work on a phone, because the browser blocks the iTunes API.
Expected result: the title "EPITUNES", a search bar, a Search button and 5 cards with "Song title", "Artist name" and a link "Open in Apple Music", without any cover. All the cards are identical for now, it's normal.
The app reloads by itself every time you edit the code. If the screen turns red, you have a syntax error and the first line of the message tells you where.
Your code only exists in the browser tab. Don't close it before you have sent us your link.
Step 1: display the songs
File: components/SongCard.tsx
First open types/song.ts. The Song type describes one song:
export type Song = {
trackId: number;
trackName: string;
artistName: string;
collectionName: string;
artworkUrl100: string;
trackViewUrl: string;
previewUrl?: string;
};
| Field | Content |
|---|---|
trackId | unique ID of the song |
trackName | title of the song |
artistName | name of the artist |
collectionName | name of the album |
artworkUrl100 | URL of the cover (100x100) |
trackViewUrl | link to the song on Apple Music |
previewUrl | URL of a 30 second preview, can be missing |
data/sampleSongs.ts contains 5 songs of this type. SongCard receives one song, but for now it displays hardcoded text.
TODO 1a: display the real title.
<Text style={styles.title}>{song.trackName}</Text>
TODO 1b: do the same for the artist.
Hint
The field is in the table above. You can also type song. in the editor: it lists all the fields of the type.
TODO 1c: add the cover of the song on the left of the card, where the TODO 1c comment is. Use the Image component, already imported at the top of the file, with the cover style, which already exists at the bottom of the file.
A picture from the internet needs a size, otherwise it is not displayed. That's what the cover style is for.
Hint
<Image source={{ uri: song.artworkUrl100 }} style={styles.cover} />
sourcetells where the picture comes from:uriis its address on the internet, here the cover of the songstyle={styles.cover}gives the picture its size
The double braces are not a typo. The first pair says that a value comes next, the second pair creates an object with one field, uri. In C, it would be close to passing a struct: (t_source){ .uri = song.artworkUrl100 }.
Expected result: 5 different cards. Each card has the cover on the left, the title in bold and the artist below.
If you make a typo in a field name, like song.trackname, the editor underlines it in red. That's TypeScript doing its job.
Step 2: call the API
File: api/itunes.ts
We use the iTunes Search API. It is free and you don't need an API key. It is simply a URL that returns JSON. Try it in your browser:
https://itunes.apple.com/search?term=daft+punk&media=music&entity=song&country=fr&limit=3
Everything you need to know about this API is in the API reference at the end of this page.
Step 3: customize your app
Files: components/SongCard.tsx and App.tsx, in the styles object at the bottom of each file.
Styles look like CSS, but they are written in an object. The names are in camelCase, the strings have quotes and the numbers have no unit.
card: {
backgroundColor: '#1c1c1e',
borderRadius: 12,
padding: 10,
},
Do at least 3 of these changes:
TODO 3: rename the app, at the top ofApp.tsx- change the background color of the screen or of the cards
- make the covers round with
borderRadiusoncover(half of the width gives a circle) - change the size or the color of the title with
fontSizeandcolor - display the name of the album under the artist with
song.collectionName
There is no expected result for this step. Do what you like, but keep it readable because you will show it at the end.
Step 4 (bonus): open the preview
File: App.tsx
Each song can have a previewUrl, a 30 second extract of the song. When you tap a card, the app calls playPreview(song), but the function is empty.
TODO 4: open the preview of the song. Linking.openURL(address) asks the phone to open an address outside the app. Linking is already imported at the top of App.tsx.
previewUrl has a ? in the type: some songs don't have one. Check that it exists before you use it.
Hint
if (!song.previewUrl) {
return;
}
Expected result: you tap a card, your phone opens the extract and plays it. Press play if it doesn't start by itself.
If you still have time:
- display the genre or the price: add the field to the
Songtype first (for exampleprimaryGenreName: string;ortrackPrice?: number;), then display it inSongCard. All the fields are listed in the API reference below. - search apps instead of songs: in the URL of
api/itunes.ts, replacemedia=music&entity=songwithmedia=software&entity=software. Apps have no preview and some fields are different, see the API reference.
Send us your app
You don't need git for this. One link per group.
- Click
Saveat the top of Snack. If Snack asks you to log in, use the same Expo account as in Expo Go. The URL of the page changes, and this new URL is the link of your project. - One person of the group fills in the form with this link and the names and contacts of everyone in the group.
It is not graded. We just want to see what you made.
Good to know
Please keep the "Open in Apple Music" link on the cards and the text "Previews provided courtesy of iTunes" at the bottom of the screen. Apple asks for them when an app uses its covers and previews.
The API is only called when you press Search. This is on purpose, because of the limit of requests.
If something doesn't work
- The screen is red: it is a syntax error, usually a missing
}or a missing quote. Ctrl + Z works. - Something is underlined in red: put your mouse on it to read the TypeScript error.
- Expo Go asks you to log in: create a free Expo account, it is the same account for Snack.
- The QR code doesn't open the app: update Expo Go, or check that your phone has internet.
- You get "Network request failed": wait a little, or switch your phone to mobile data.
- For anything else, ask us.
Documentation
- Expo
- Expo tutorial
- React Native basics
- React Native components
- Linking, used in the bonus
- iTunes Search API
iTunes Search API reference
The API is public: no account and no key. It has two endpoints, search and lookup, and both answer in JSON. Official documentation: performance-partners.apple.com/search-api
Search
https://itunes.apple.com/search?parameter1=value1¶meter2=value2
term(required): the text to search, URL encoded (spaces become+)country(recommended): two-letter country code, likefr,usorgb. Default:usmedia:music,software,podcast,movie,musicVideo,audiobook,ebook,tvShow,shortFilmorall. Default:allentity: the type of result, it depends onmedia. Formusic:song,album,musicArtist,musicVideo. Forsoftware:software,iPadSoftware,desktopSoftwareattribute: where to look for the term. Formusic:artistTerm,songTerm,albumTerm... Default: everywherelimit: maximum number of results, from 1 to 200. Default: 50lang:en_usorja_jp. Default:en_usexplicit:YesorNo. Default:Yes
Examples:
- Songs whose artist matches "daft punk", French store, 10 results:
https://itunes.apple.com/search?term=daft+punk&media=music&entity=song&attribute=artistTerm&country=fr&limit=10 - Albums:
https://itunes.apple.com/search?term=daft+punk&media=music&entity=album&country=fr - Apps:
https://itunes.apple.com/search?term=notes&media=software&country=fr&limit=5
Lookup
Gets items from their ID instead of searching by text. The IDs come from a search result (trackId, artistId, collectionId).
- One item:
https://itunes.apple.com/lookup?id=909253 - The albums of an artist:
https://itunes.apple.com/lookup?id=909253&entity=album - Several items at once: put several IDs separated by commas.
With entity, the first element of results is the item you asked for (here the artist), and the related items come after it.
Response
{
"resultCount": 1,
"results": [
{
"wrapperType": "track",
"kind": "song",
"trackId": 120954025,
"artistId": 909253,
"trackName": "Upside Down",
"artistName": "Jack Johnson",
"collectionName": "Sing-a-Longs and Lullabies for the Film Curious George",
"artworkUrl100": "https://...",
"previewUrl": "https://...",
"trackViewUrl": "https://...",
"trackTimeMillis": 210743,
"trackPrice": 0.99,
"currency": "USD",
"primaryGenreName": "Rock"
}
]
}
In TypeScript, the response looks like this:
type SearchResponse = {
resultCount: number;
results: Song[];
};
Main fields:
| Field | Content |
|---|---|
wrapperType | track, collection or artist |
kind | song, music-video, podcast... |
trackId, artistId, collectionId | IDs, to use with lookup |
trackName | title of the song (or name of the app) |
artistName | artist (or developer of the app) |
collectionName | album |
artworkUrl60, artworkUrl100 | cover, 60x60 or 100x100 pixels |
previewUrl | 30 second preview, only for tracks |
trackViewUrl | page of the item on Apple Music or on the App Store |
trackTimeMillis | duration in milliseconds |
trackPrice, collectionPrice, currency | prices |
primaryGenreName | genre |
releaseDate | release date, ISO format |
trackExplicitness | explicit, cleaned or notExplicit |
Apps (media=software) have other fields, like formattedPrice, averageUserRating, description or artworkUrl512, and no previewUrl. Use console.log to see everything you get.
Limits and rules
- About 20 requests per minute, and Apple says this can change. Above the limit, the API refuses your requests for a while.
- The whole room probably shares the same IP address, so don't spam the
Searchbutton. If the API stops answering, wait one minute. - Covers and previews can only be used to promote the content of Apple's stores: keep a link to the store next to them, say that previews are provided courtesy of iTunes, and play previews in streaming instead of downloading them.
Contact
This workshop was made by Epimac. If you liked it, come and see us on our Linktree.