- Why?
- How?
- What?
- When?
Use Google and Youtube search engines. Learn to search for answers for your questions.
Try:
- https://www.google.com/?q=how%20to%20create%20mobile%20applications
- https://www.youtube.com/results?search_query=how+to+create+mobile+applications
Usually Youtube provides very good answers, so step by step, you get smarter, for what to search for.
(Ok, we do some tasks in class, but Youtube is much smarter, than your teacher.)
https://developer.apple.com/swift/
Running either limited set of functionality, or in Webbrowser.
- https://www.swift.org/blog/swift-on-windows/
- https://medium.com/@illescasDaniel/run-swift-on-windows-11-in-2021-675d703e7a91
- https://www.developer.com/microsoft/an-intro-to-swift-programming-on-windows-even/
- https://developer.android.com/studio
- https://developer.android.com/jetpack
- https://developer.android.com/kotlin
- https://developer.android.com/games
To have fast application, that consumes less battery, native solutions are usually better, providing additional features (like running in background) etc.
For application with GUI development:
- https://www.sam-solutions.com/blog/cross-platform-mobile-development/ (summary list)
- https://kotlinlang.org/docs/multiplatform-mobile-getting-started.html
- https://reactnative.dev/
For game development:
Web2.0 application will work in web browser, on any device.
- Create yourself a Github account (for example github.com/your_user_name)
- Create yourself a new public repository and name it your_user_name.github.io (for example for me it is mxrguspxrt.github.io)
- Visit https://mxrguspxrt.github.io/ and your site
- Create yourself a simple homepage using Markdown or HTML
- On this simple homepage write a essay on a subject: "Why I am learning in this school, how I hope it helps me in the future, who I want to be when I grow up"
- Task 5 will be graded on scale of 1-5 before next class. Send me link to your_user_name.github.io.
By the next class:
- Have team
- Have idea for startup and Figma landing page design
- Next class we start with dev env setup and programming!
Today, we will be installing React Native with Expo.
Install Expo: https://docs.expo.dev/get-started/installation/
Read documentation + Install React Native: https://reactnative.dev/docs/environment-setup
- Let's install support for Android: https://developer.android.com/studio (Download Studio + run the installer)
- Let's install support for IOS: https://developer.apple.com/xcode/ (Download XCode + run the installer)
- Let's install support for web:
npx expo install react-native-web@~0.18.7 react-dom@18.0.0 @expo/webpack-config@^0.17.0 # (these are required dependecies)
npx create-expo-app Hello
cd Hello
npm run android
# https://docs.expo.dev/workflow/android-studio-emulator (here are steps you need to do)
npm run ios
# this part will be skipped, if you are on Windows machine. But you can test on some OSX machine on your own.
npx expo install react-native-web@~0.18.7 react-dom@18.0.0 @expo/webpack-config@^0.17.0 # (these are required dependecies)
npm run web
Ideally, you will have Android and web running, maybe iOS also. If you have any problems, ask teacher or co-student for help.
Task:
- Implement at least 1 element (for example Text or Button from your Figma design).
- Upload code for Expo project + Link to Figma to Github
- Send the teacher's email the link to Github project
- Your blog has posts. Post has title, text, created_at and picture. Posts are listed so that the latest is first to be seen (ordered by creation date).
- On Index Page you will see list of Blog Posts. In list, only title and summary is shown.
- When you click on Blog Post on Index Page, you will see all text of Blog Post.
- Ideally, there is possibility to comment also.
- If you want to use Icons, you can use https://www.figma.com/community/file/1135544811622139774
- To get additional visual resources, you can search them from https://www.figma.com/community and https://www.figma.com/community/search?resource_type=mixed&sort_by=popular&query=buttons&editor_type=all (what Chart and Maps you can find and reusein Figma community?)
As a result, you should have designed 2 views in Figma (blog posts lists and details view).
Start from understanding how React Native components work:
- Read everything from Tutorials section (https://docs.expo.dev/tutorial/planning/)
- Read how to use SVG from React Native (https://docs.expo.dev/ui-programming/using-svgs/)
- Check different UI libraries supported by React Native (https://docs.expo.dev/guides/userinterface/)
Check this first: https://reactnative.dev/docs/components-and-apis#basic-components
Your initial code looks like this
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';
export default function App() {
return (
<View style={styles.container}>
<Text>Open up App.js to start working on your app!</Text>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Let's make a new component called BlogPostPreview, that has 3 arguments: title, content and image.
Important links to read:
- https://reactnative.dev/docs/view and function component)
- https://reactnative.dev/docs/image
const BlogPostPreview = ({title, content, imageUrl}) => {
return (
<View>
<Text style={{fontSize: "2em"}}>{title}</Text>
<Text>{content}</Text>
<Image
style={{width: 100, height: 100}}
source={{
uri: imageUrl
}}
/>
</View>
);
};
Try will you get this code to work so that first blog posts previews are listed on the screen?
Hints:
- You need to add last code section into existing code.
- You need to call it from your code.
- You need to add imports.
PLEASE TRY ON YOUR OWN TO GET IT TO WORK!
.. .. .. .. .. ..
Final code should look something similar to this.
import { StyleSheet, Text, View, Image} from 'react-native';
const BlogPostPreview = ({title, content, imageUrl}) => {
return (
<View>
<Text style={{fontSize: "2em"}}>{title}</Text>
<Text>{content}</Text>
<Image
style={{width: 100, height: 100}}
source={{
uri: imageUrl
}}
/>
</View>
);
};
export default function App() {
return (
<View style={styles.container}>
<BlogPostPreview
title="My first post"
content="This is short text of the post"
imageUrl="https://github.com/mxrguspxrt/mobile/raw/main/cat1.jpeg"
/>
<BlogPostPreview
title="My second post"
content="This is short text of the post"
imageUrl="https://github.com/mxrguspxrt/mobile/raw/main/cat2.jpeg"
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
- Align posts to the left.
- Add header to your site (with Blog name)
- Cat images come from my Github - upload your custom images to your Github account. How could you upload images to your project without uploading them to internet? (Assets folder + check this manual https://reactnative.dev/docs/images). You should rename imageUrl to just image and use
image={require("./assets/cat2.jpeg")}
as prop to your component.
https://reactnavigation.org/docs/hello-react-navigation and https://reactnative.dev/docs/navigation#react-navigation (You should be using expo version.)
- Create a new Screen and components for details view.
- When user clicks in List view on item, details view opens.
- In details view, there are also comments.
PLEASE TRY ON YOUR OWN TO GET IT TO WORK!
.. .. .. .. .. ..
Your final code should look something like:
import { StyleSheet, Text, View, Image } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
const BlogPostPreview = ({ title, content, imageUrl }) => {
return (
<View>
<Text style={{ fontSize: "2em" }}>{title}</Text>
<Text>{content}</Text>
<Image
style={{ width: 100, height: 100 }}
source={{
uri: imageUrl
}}
/>
</View>
);
};
const BlogListScreen = () => {
return (
<View style={styles.container}>
<BlogPostPreview
title="My first post"
content="This is short text of the post"
imageUrl="https://github.com/mxrguspxrt/mobile/raw/main/cat1.jpeg"
/>
<BlogPostPreview
title="My second post"
content="This is short text of the post"
imageUrl="https://github.com/mxrguspxrt/mobile/raw/main/cat2.jpeg"
/>
</View>
);
}
const BlogPostDetails = ({ title, content, imageUrl }) => {
return (
<View>
<Text style={{ fontSize: "2em" }}>{title}</Text>
<Text>{content}</Text>
<Image
style={{ width: 200, height: 200 }}
source={{
uri: imageUrl
}}
/>
<Text>Add comments here</Text>
</View>
);
};
const BlogDetailsScreen = () => {
return (
<View style={styles.container}>
<BlogPostDetails
title="My first post"
content="This is short text of the post"
imageUrl="https://github.com/mxrguspxrt/mobile/raw/main/cat1.jpeg"
/>
</View>
);
}
const Stack = createNativeStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={BlogListScreen}
options={{ title: 'Welcome' }}
/>
<Stack.Screen name="Details" component={BlogDetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
- When you click item in list view, details open
- Details view must have comments
- Create blog-posts.json file in your Github that contains list of blog posts
- Load data https://reactnative.dev/docs/network
Structure of blog-posts.json could look something similar:
{
"count": 2,
"posts": [
{"title": "yay", "content": "no way", "imageUrl": "https://github.com/mxrguspxrt/mobile/raw/main/cat1.jpeg"},
{"title": "nay", "content": "hosey", "imageUrl": "https://github.com/mxrguspxrt/mobile/raw/main/cat2.jpeg"}
]
}
You need to click Raw button in Github, this takes me to https://raw.githubusercontent.com/mxrguspxrt/mobile/main/blog-posts.json and I can use this URL in my code.
PLEASE TRY ON YOUR OWN TO GET IT TO WORK!
.. .. .. .. .. ..
Final code should change something similar to this:
import { ActivityIndicator, FlatList, StyleSheet, Text, View, Image } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import React, { useEffect, useState } from 'react';
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
const BlogPostPreview = ({ title, content, imageUrl }) => {
return (
<View>
<Text style={{ fontSize: "2em" }}>{title}</Text>
<Text>{content}</Text>
<Image
style={{ width: 100, height: 100 }}
source={{
uri: imageUrl
}}
/>
</View>
);
};
const BlogListScreen = () => {
const [isLoading, setLoading] = useState(true);
const [data, setData] = useState([]);
const getBlogPostsFromApi = async () => {
try {
const response = await fetch('https://raw.githubusercontent.com/mxrguspxrt/mobile/main/blog-posts.json');
const json = await response.json();
setData(json.posts);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
}
useEffect(() => {
getBlogPostsFromApi();
}, []);
if (isLoading) {
return <View style={styles.container}><ActivityIndicator /></View>
}
return <FlatList
data={data}
keyExtractor={({ id }, index) => id}
renderItem={({ item }) => (
<BlogPostPreview
title={item.title}
content={item.content}
imageUrl={item.imageUrl}
/>
)}
/>
}
const BlogPostDetails = ({ title, content, imageUrl }) => {
return (
<View>
<Text style={{ fontSize: "2em" }}>{title}</Text>
<Text>{content}</Text>
<Image
style={{ width: 200, height: 200 }}
source={{
uri: imageUrl
}}
/>
<Text>Add comments here</Text>
</View>
);
};
const BlogDetailsScreen = () => {
return (
<View style={styles.container}>
<BlogPostDetails
title="My first post"
content="This is short text of the post"
imageUrl="https://github.com/mxrguspxrt/mobile/raw/main/cat1.jpeg"
/>
</View>
);
}
const Stack = createNativeStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={BlogListScreen}
options={{ title: 'Welcome' }}
/>
<Stack.Screen name="Details" component={BlogDetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
We start with simple in memory database (that you can replace with Postgre or any other preferred DB in the future):
// mkdir express_blog
// cd express_blog
// npm install express --save
// node my-server.js
// save following contents into my-server.json
// mkdir express_blog
// cd express_blog
// npm install express --save
// node my-server.js
// save following contents into my-server.json
const express = require('express')
const app = express()
const port = 3000
let postsData = {
"count": 2,
"posts": [
{
"id": "1",
"title": "My first post",
"content": "Yay!"
},
{
"id": "2",
"title": "My second post",
"content": "Yay!"
}
]
}
let commentsData = [
{
postId: "1",
name: "Batman",
comment: "Nice post"
},
{
postId: "2",
name: "Batman",
comment: "Also nice post"
},
{
postId: "1",
name: "Superman",
comment: "I do not agree"
},
]
app.get('/posts', (req, res) => {
res.header("Access-Control-Allow-Origin", "*")
res.json(postsData)
})
app.post('/add-comment', (req, res) => {
commentsData.push(req.comment);
res.json(postsData)
})
app.get('/post/:postId', (req, res) => {
res.json(postsData.posts.find(x => x.id == req.params.postId))
})
app.get('/post/:postId/comments', (req, res) => {
res.json(commentsData.filter(x => x.postId == req.params.postId))
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
Task:
- Integrate http://localhost:3000 with your app
- Add functionality to add posts
Hints:
- In Expo app you need to change URL from which posts are fetched
- Once this URL has been fixed, your data loading still does not work. Please see error for Mobile App in console. (Error should be related to CORS headers. Using Google, find fix for your Express server. https://expressjs.com/en/resources/middleware/cors.html)
By now you should have:
- Express server running and data is loaded from data into React Native app list view for posts
- When you click on post in list view, it opens up details view (https://reactnative.dev/docs/navigation)
// you should change your code for navigation something like this
const BlogPostPreview = ({ id, title, content, imageUrl, navigation }) => {
return (
<View onClick={() => navigation.navigate('Details', { id, title, content, imageUrl })}>
If either of those points is not working, please try to get to work on your own. Or contact teacher.
Now please:
- add https://reactnative.dev/docs/textinput and https://reactnative.dev/docs/button for comment form in post details view (Name TextInput, Comment TextInput, Submit comment button).
- add action, that if Submit comment button is pressed, then POST is done to http://localhost:3000/add-comment (that is your express server) (https://reactnative.dev/docs/network)
- to verify, that comment was added visit: http://localhost:3000/post/1/comments
Please try to get it to work on your own (imports, onChangeText, onPress, fetch etc are done correctly?) Use "console.log" to debug!
..
..
..
..
Here is final code for React Native (do not cheat and do not use this, you should do it own your own):
import { ActivityIndicator, FlatList, StyleSheet, Text, View, Image, TextInput, Button, SafeAreaView } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import React, { useEffect, useState } from 'react';
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
const BlogPostPreview = ({ id, title, content, imageUrl, navigation }) => {
return (
<View onClick={() => navigation.navigate('Details', { id, title, content, imageUrl })}>
<Text style={{ fontSize: "2em" }}>{title}</Text>
<Text>{content}</Text>
<Image
style={{ width: 100, height: 100 }}
source={{
uri: imageUrl
}}
/>
</View>
);
};
const BlogListScreen = ({ navigation }) => {
const [isLoading, setLoading] = useState(true);
const [data, setData] = useState([]);
const getBlogPostsFromApi = async () => {
try {
const response = await fetch('http://localhost:3000/posts');
const json = await response.json();
setData(json.posts);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
}
useEffect(() => {
getBlogPostsFromApi();
}, []);
if (isLoading) {
return <View style={styles.container}><ActivityIndicator /></View>
}
return <FlatList
data={data}
keyExtractor={({ id }, index) => id}
renderItem={({ item }) => (
<BlogPostPreview
id={item.id}
title={item.title}
content={item.content}
imageUrl={item.imageUrl}
navigation={navigation}
/>
)}
/>
}
const BlogPostDetails = ({ id, title, content, imageUrl }) => {
const [name, onChangeName] = React.useState("Batman");
const [comment, onChangeComment] = React.useState("BATMAN!!");
const sendComment = () => {
fetch('http://localhost:3000/add-comment', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name,
comment,
postId: id
})
});
}
return (
<SafeAreaView>
<Text style={{ fontSize: "2em" }}>{title}</Text>
<Text>{content}</Text>
<Image
style={{ width: 200, height: 200 }}
source={{
uri: imageUrl
}}
/>
<Text>Add comments here</Text>
<Text>Name:</Text>
<TextInput style={{ border: "thin solid black" }} value={name} onChangeText={onChangeName} />
<Text>Comment:</Text>
<TextInput style={{ border: "thin solid black" }} value={comment} onChangeText={onChangeComment} />
<Button onPress={() => sendComment()} title="Send comment " />
</SafeAreaView>
);
};
const BlogDetailsScreen = () => {
return (
<View style={styles.container}>
<BlogPostDetails
id="1"
title="My first post"
content="This is short text of the post"
imageUrl="https://github.com/mxrguspxrt/mobile/raw/main/cat1.jpeg"
/>
</View>
);
}
const Stack = createNativeStackNavigator();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={BlogListScreen}
options={{ title: 'Welcome' }}
/>
<Stack.Screen name="Details" component={BlogDetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
Here is final code for Express server (do not cheat and do not use this, you should do it own your own):
// mkdir express_blog
// cd express_blog
// npm install express --save
// node my-server.js
// save following contents into my-server.json
const express = require('express')
const cors = require('cors')
const app = express()
app.use(cors())
app.use(express.json());
const port = 3000
let postsData = {
"count": 2,
"posts": [
{
"id": "1",
"title": "My first post",
"content": "Yay!"
},
{
"id": "2",
"title": "My second post",
"content": "Yay!"
}
]
}
let commentsData = [
{
postId: "1",
name: "Batman",
comment: "Nice post"
},
{
postId: "2",
name: "Batman",
comment: "Also nice post"
},
{
postId: "1",
name: "Superman",
comment: "I do not agree"
},
]
app.get('/posts', (req, res) => {
res.json(postsData)
})
app.post('/add-comment', (req, res) => {
console.log("Received post on /add-comment", req.body)
commentsData.push(req.body);
res.json(postsData)
})
app.get('/post/:postId', (req, res) => {
res.json(postsData.posts.find(x => x.id == req.params.postId))
})
app.get('/post/:postId/comments', (req, res) => {
res.json(commentsData.filter(x => x.postId == req.params.postId))
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})