Mobile Solutions
Knowledge Check Answers
-
useStatereturns an array of two values: the current state value and a function to update it. Example:const [messages, setMessages] = useState([]). -
useEffectwith[]callsfetchMessagesonce when the component first loads. Calling it manually (e.g., after sending a message) is for refreshing the list immediately. -
FlatListis virtualized — it only renders the items visible on screen.ScrollViewrenders all items at once, which is slow for long chat histories. -
response.json()parses the HTTP response body from JSON format into a JavaScript object. -
It adapts keyboard behavior per platform: iOS uses
"padding"(resize the view), Android uses"height"(resize with height adjustment).
Challenge 1 Solution
Add to the state:
const handleClearChat = async () => {
try {
await fetch(`${API_BASE_URL}/api/messages`, { method: "DELETE" });
setMessages([]);
} catch (err) {
console.error("Failed to clear:", err);
}
};
Add a button in the header:
<View style={styles.header}>
<Text style={styles.headerTitle}>Agentic Chat</Text>
<TouchableOpacity onPress={handleClearChat}>
<Text style={{ color: "#FFF", fontSize: 14 }}>Clear</Text>
</TouchableOpacity>
</View>
Update header style:
header: {
backgroundColor: "#007AFF",
paddingTop: 50,
paddingBottom: 15,
paddingHorizontal: 16,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
Challenge 2 Solution
Add an avatar next to each bubble:
const renderMessage = ({ item }: { item: Message }) => {
const isUser = item.sender === "user";
return (
<View style={[styles.messageRow, isUser && styles.messageRowUser]}>
{!isUser && (
<View style={styles.avatar}>
<Text style={styles.avatarText}>A</Text>
</View>
)}
<View style={[styles.bubble, isUser ? styles.userBubble : styles.aiBubble]}>
<Text style={[styles.bubbleText, isUser ? styles.userText : styles.aiText]}>
{item.content}
</Text>
</View>
{isUser && (
<View style={[styles.avatar, styles.userAvatar]}>
<Text style={[styles.avatarText, styles.userAvatarText]}>U</Text>
</View>
)}
</View>
);
};
Styles:
messageRow: { flexDirection: "row", marginBottom: 8, alignItems: "flex-end" },
messageRowUser: { justifyContent: "flex-end" },
avatar: {
width: 30,
height: 30,
borderRadius: 15,
backgroundColor: "#DDD",
justifyContent: "center",
alignItems: "center",
marginHorizontal: 6,
},
userAvatar: { backgroundColor: "#007AFF" },
avatarText: { color: "#666", fontWeight: "bold", fontSize: 14 },
userAvatarText: { color: "#FFF" },
Challenge 3 Solution
const [showScrollButton, setShowScrollButton] = useState(false);
const handleScroll = (event: any) => {
const offsetY = event.nativeEvent.contentOffset.y;
setShowScrollButton(offsetY > 200);
};
const scrollToBottom = () => {
flatListRef.current?.scrollToEnd({ animated: true });
};
Add to FlatList:
<FlatList
onScroll={handleScroll}
scrollEventThrottle={100}
...
/>
Add floating button:
{showScrollButton && (
<TouchableOpacity style={styles.scrollButton} onPress={scrollToBottom}>
<Text style={styles.scrollButtonText}>↓</Text>
</TouchableOpacity>
)}
Styles:
scrollButton: {
position: "absolute",
bottom: 80,
right: 16,
backgroundColor: "#007AFF",
width: 40,
height: 40,
borderRadius: 20,
justifyContent: "center",
alignItems: "center",
elevation: 4,
shadowColor: "#000",
shadowOpacity: 0.2,
shadowRadius: 4,
},
scrollButtonText: { color: "#FFF", fontSize: 20 },
Open-Ended Project: Multi-Provider Chat
This is a larger project. Here's a starting approach:
1. Add provider state
const [provider, setProvider] = useState("openai");
const [customApiKey, setCustomApiKey] = useState("");
const [showSettings, setShowSettings] = useState(false);
2. Settings modal
{showSettings && (
<View style={styles.settingsOverlay}>
<View style={styles.settingsPanel}>
<Text style={styles.settingsTitle}>Settings</Text>
<Text>Provider:</Text>
{["openai", "anthropic", "deepseek"].map((p) => (
<TouchableOpacity key={p} onPress={() => setProvider(p)}>
<Text style={{ fontWeight: provider === p ? "bold" : "normal" }}>{p}</Text>
</TouchableOpacity>
))}
<TextInput
placeholder="API Key"
value={customApiKey}
onChangeText={setCustomApiKey}
secureTextEntry
/>
<TouchableOpacity onPress={() => setShowSettings(false)}>
<Text>Done</Text>
</TouchableOpacity>
</View>
</View>
)}
3. Send provider info in request headers
const response = await fetch(`${API_BASE_URL}/api/chat`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Provider": provider,
"X-Api-Key": customApiKey,
},
body: JSON.stringify({ message: text }),
});
4. Backend reads headers (in routes.py)
provider = request.headers.get("X-Provider", "openai")
api_key = request.headers.get("X-Api-Key") or os.getenv("OPENAI_API_KEY")
if provider == "anthropic":
# Call Anthropic API
elif provider == "deepseek":
# Call DeepSeek API (OpenAI-compatible)
else:
# Default OpenAI call