← Back to Personal Project

Toulouse Sport - Mobile Application

Introduction

Toulouse Sport is a cross-platform mobile application built with React Native and Expo, designed to connect sports enthusiasts in Toulouse. The app allows users to discover sports activities, find practice partners, join community events, and organize their own sports sessions. With a focus on social connectivity and real-time updates, the application provides a seamless experience for building an active sports community.

Key Objectives:

Version: 1.0.0
Platform: iOS, Android
Status: Active Development


Technology Stack

Frontend Framework

UI Components & Navigation

Backend & Services

Media & Assets

Development Tools

App Screenshot


Core Features

1. Authentication System

Secure user authentication powered by Supabase Auth:

Implementation:

2. User Profile Management

Comprehensive profile system:

Database Schema:

3. Home Feed

Dynamic activity feed showing:

Components:

4. Sports Discovery

Browse and explore sports:

Implementation:

5. Map Integration

Location-based features (in development):

Component: MapScreen.tsx

6. Friends & Social

Build your sports network:

Component: Friends.tsx

7. Navigation System

Intuitive multi-level navigation:

Navigation Structure:

AppNavigator (Stack)
├── Auth (if not signed in)
└── Navigation (Bottom Tabs)
    ├── Home (Stack)
    │   ├── Home Feed
    │   └── Activity Details
    ├── Sports (Stack)
    │   ├── Sport List
    │   └── Sport Details
    ├── Map (Stack)
    │   └── Map View
    └── Profile (Stack)
        ├── Profile View
        ├── Edit Profile
        └── Settings

8. Settings & Preferences

Customization options:

Component: Parameter.tsx


Architecture & Implementation

Project Structure

toulouse-sport/
├── App.tsx                    # Root component with session management
├── components/                # Reusable UI components
│   ├── Auth.tsx              # Authentication form
│   ├── Account.tsx           # User account management
│   ├── AccountFilled.tsx     # Profile header component
│   ├── AppNavigator.tsx      # Navigation configuration
│   ├── Avatar.tsx            # Avatar display component
│   ├── Card/
│   │   └── HomeCard.tsx      # Activity card component
│   └── Icons (Account, Parameter, SignOut)
├── src/                       # Main application screens
│   ├── Home.tsx              # Home feed screen
│   ├── SportList.tsx         # Sports catalog
│   ├── MapScreen.tsx         # Map view
│   ├── Profile.tsx           # User profile
│   ├── Friends.tsx           # Friends list
│   ├── Parameter.tsx         # Settings
│   ├── Navigation.tsx        # Tab navigator
│   └── AvatarProfile.tsx     # Avatar editing
├── lib/
│   ├── supabase.ts           # Supabase client configuration
│   └── types.ts              # TypeScript type definitions
├── supabase/
│   ├── config.toml           # Supabase configuration
│   ├── seed.sql              # Database seed data
│   └── migrations/           # Database schema migrations
├── assets/                    # Images and static files
├── android/                   # Android native code
└── ios/                       # iOS native code

Authentication Flow

Session Management:

// App.tsx - Root level session handling
const [session, setSession] = useState<Session | null>(null)

useEffect(() => {
  // Get initial session
  supabase.auth.getSession().then(({ data: { session } }) => {
    setSession(session)
  })

  // Listen for auth changes
  supabase.auth.onAuthStateChange((_event, session) => {
    setSession(session)
  })
}, [])

// Conditional rendering based on auth state
const isSignedIn = session && session.user;
return (
  <View>
    {isSignedIn ? <Navigation session={session} /> : <Auth />}
  </View>
)

Authentication Component:

Database Schema

Database Migrations (7 migrations):

  1. user_management_starter (2024-08-24): Initial user authentication setup
  2. add_age (2024-08-24): Added age field to profiles
  3. sport_list (2025-04-26): Sports catalog table
  4. around_card (2025-04-27): Nearby activities feature
  5. profile_user (2025-04-27): Enhanced profile fields
  6. signed_card (2025-04-27): User activity signups
  7. home_card (2025-05-04): Home feed activities

Key Tables:

profiles:

sport_list:

home_card (Activities):

signed_card (Activity Signups):

Row Level Security (RLS):

Database Schema

Image Management

Supabase Storage Integration:

// SportList.tsx - Image loading from storage
async function downloadImage(path: string) {
  try {
    const { data, error } = await supabase.storage
      .from('sports')
      .download(path)
    
    if (error) throw error

    const fr = new FileReader()
    fr.readAsDataURL(data)
    fr.onload = () => {
      setSportUrl(fr.result as string)
    }
  } catch (error) {
    console.log('Error downloading image:', error.message)
  }
}

Features:

State Management

Local State: React hooks (useState, useEffect)

Server State: Supabase real-time subscriptions (planned)

Type Safety

TypeScript Integration:

// lib/types.ts - Database type definitions
export type Tables<T extends keyof Database['public']['Tables']> = 
  Database['public']['Tables'][T]['Row']

type SportList = Tables<"sport_list">
type Profile = Tables<"profiles">
type HomeCard = Tables<"home_card">

Benefits:


Development Process

Setup & Configuration

1. Project Initialization:

# Create Expo app with TypeScript
npx create-expo-app toulouse-sport --template expo-template-blank-typescript

# Install dependencies
npm install @supabase/supabase-js @react-navigation/native
npm install expo-image-picker react-native-vector-icons

2. Supabase Configuration:

// lib/supabase.ts
import { createClient } from '@supabase/supabase-js'
import AsyncStorage from '@react-native-async-storage/async-storage'

const supabaseUrl = 'YOUR_SUPABASE_URL'
const supabaseAnonKey = 'YOUR_SUPABASE_ANON_KEY'

export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
  auth: {
    storage: AsyncStorage,
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: false,
  },
})

3. Platform-Specific Setup:

Build & Deployment

Development:

# Start development server
npm start

# Run on iOS simulator
npm run ios

# Run on Android emulator
npm run android

# Web development
npm run web

Production Builds:

# iOS build (requires Apple Developer account)
eas build --platform ios

# Android build (APK or AAB)
eas build --platform android

# Submit to App Store / Play Store
eas submit

Testing Strategy

Manual Testing:

Planned Automated Testing:


Challenges & Solutions

Challenge 1: Cross-Platform Consistency

Problem: UI differences between iOS and Android Solution:

Challenge 2: Image Loading Performance

Problem: Slow image loading from Supabase Storage Solution:

Challenge 3: Session Management

Problem: Session persistence across app restarts Solution:

Challenge 4: Database Migrations

Problem: Evolving schema during development Solution:

Challenge 5: Type Safety

Problem: Maintaining type consistency between frontend and database Solution:


Key Features Implementation

Sports Catalog with Images

// SportList.tsx - Rendering sports with images
function Sport({ sport }: { sport: SportList }) {
  const [sportUrl, setSportUrl] = useState<string | null>(null)

  useEffect(() => {
    sport.sport_image && downloadImage(sport.sport_image)
  }, [sport])

  return (
    <View style={styles.sportContainer}>
      {sportUrl ? (
        <Image
          source={{ uri: sportUrl }}
          style={styles.sportImage}
          defaultSource={require('../assets/icon.png')}
          onError={(error) => console.warn('Image load failed:', error)}
        />
      ) : (
        <ActivityIndicator />
      )}
      <Text style={styles.sportName}>{sport.sport_name}</Text>
    </View>
  )
}

Avatar Upload with Image Picker

// AvatarProfile.tsx - Profile picture upload
const pickImage = async () => {
  const result = await ImagePicker.launchImageLibraryAsync({
    mediaTypes: ImagePicker.MediaTypeOptions.Images,
    allowsEditing: true,
    aspect: [1, 1],
    quality: 0.8,
  })

  if (!result.canceled) {
    uploadAvatar(result.assets[0].uri)
  }
}

const uploadAvatar = async (uri: string) => {
  const ext = uri.split('.').pop()
  const fileName = `${session.user.id}.${ext}`
  
  const { error } = await supabase.storage
    .from('avatars')
    .upload(fileName, file, { upsert: true })
  
  if (!error) {
    updateProfile({ avatar_url: fileName })
  }
}

Bottom Tab Navigation

// Navigation.tsx - Tab navigator setup
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'

const Tab = createBottomTabNavigator()

export default function Navigation({ session }: { session: Session }) {
  return (
    <NavigationContainer>
      <Tab.Navigator
        screenOptions={{
          tabBarActiveTintColor: '#007AFF',
          tabBarInactiveTintColor: 'gray',
        }}
      >
        <Tab.Screen 
          name="Home" 
          component={Home}
          options={{
            tabBarIcon: ({ color, size }) => (
              <Icon name="home" size={size} color={color} />
            ),
          }}
        />
        <Tab.Screen name="Sports" component={SportList} />
        <Tab.Screen name="Map" component={MapScreen} />
        <Tab.Screen name="Profile" component={Profile} />
      </Tab.Navigator>
    </NavigationContainer>
  )
}

Performance Optimization

Image Optimization

List Rendering

Network Optimization

Bundle Size


Security Considerations

Authentication

Database Security

Data Privacy

API Security


Future Enhancements

Phase 1: Core Features (Q1 2025)

Phase 2: Social Features (Q2 2025)

Phase 3: Advanced Features (Q3 2025)

Phase 4: Growth & Scale (Q4 2025)


Lessons Learned

Technical Insights

Development Process

Design Decisions


Project Metrics

Development Timeline:

Codebase Statistics:

Dependencies: 25+ npm packages

Platform Support:


Conclusion

Toulouse Sport represents a comprehensive full-stack mobile development project, demonstrating proficiency in:

The application provides a solid foundation for building a sports community platform and showcases the ability to design, develop, and deploy production-ready mobile applications. The modular architecture and clean code practices ensure maintainability and scalability for future enhancements.

Key Achievements:

The project continues to evolve with new features and improvements, demonstrating ongoing commitment to delivering a high-quality user experience for the Toulouse sports community.