πŸ“± Mobile Development Mastery

Build iOS and Android apps for Canada's growing mobile market

8 Development Paths
60+ Code Examples
5 Real Apps Built

Course Overview

Duration: 16-20 weeks (self-paced)

Level: Beginner to Advanced

Prerequisites: Basic programming knowledge, preferably JavaScript or Swift

Development Paths Covered

  • Native iOS Development with Swift and SwiftUI
  • Native Android Development with Kotlin
  • Cross-platform with React Native
  • Cross-platform with Flutter (Dart)
  • Progressive Web Apps (PWAs)
  • App Store optimization and deployment
  • Mobile analytics and monetization
  • Canadian market strategies and localization
Mobile app development workspace

Learning Modules

🍎 Module 1: iOS Development with Swift

Beginner

Topics Covered:

  • Swift programming language fundamentals
  • Xcode IDE and Interface Builder
  • UIKit and SwiftUI frameworks
  • Navigation and data persistence

Code Example - Toronto Weather App (Swift):

import SwiftUI
import CoreLocation

// Toronto Weather App using SwiftUI
struct TorontoWeatherApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct ContentView: View {
    @StateObject private var weatherManager = WeatherManager()
    @State private var showingAlert = false
    
    var body: some View {
        NavigationView {
            ZStack {
                // Toronto-themed background
                LinearGradient(
                    colors: [Color.blue, Color.gray.opacity(0.3)],
                    startPoint: .topLeading,
                    endPoint: .bottomTrailing
                )
                .ignoresSafeArea()
                
                VStack(spacing: 20) {
                    // Toronto City Header
                    HStack {
                        Image(systemName: "location.fill")
                            .foregroundColor(.white)
                        Text("Toronto, ON")
                            .font(.title)
                            .fontWeight(.bold)
                            .foregroundColor(.white)
                    }
                    
                    // Weather Information
                    if let weather = weatherManager.currentWeather {
                        VStack(spacing: 10) {
                            Text("\(weather.temperature)Β°C")
                                .font(.largeTitle)
                                .fontWeight(.heavy)
                                .foregroundColor(.white)
                            
                            Text(weather.description)
                                .font(.headline)
                                .foregroundColor(.white.opacity(0.9))
                            
                            HStack(spacing: 30) {
                                WeatherDetailView(
                                    icon: "humidity.fill",
                                    title: "Humidity",
                                    value: "\(weather.humidity)%"
                                )
                                
                                WeatherDetailView(
                                    icon: "wind",
                                    title: "Wind",
                                    value: "\(weather.windSpeed) km/h"
                                )
                                
                                WeatherDetailView(
                                    icon: "thermometer",
                                    title: "Feels Like",
                                    value: "\(weather.feelsLike)Β°C"
                                )
                            }
                        }
                        .padding()
                        .background(
                            RoundedRectangle(cornerRadius: 20)
                                .fill(.ultraThinMaterial)
                        )
                    } else {
                        ProgressView("Loading Toronto weather...")
                            .foregroundColor(.white)
                    }
                    
                    // TTC Integration Button
                    Button("Check TTC Delays") {
                        weatherManager.checkTTCStatus()
                        showingAlert = true
                    }
                    .padding()
                    .background(Color.red)
                    .foregroundColor(.white)
                    .cornerRadius(10)
                    
                    Spacer()
                }
                .padding()
            }
        }
        .onAppear {
            weatherManager.requestLocation()
        }
        .alert("TTC Status", isPresented: $showingAlert) {
            Button("OK") { }
        } message: {
            Text("TTC services are running normally on all lines.")
        }
    }
}

struct WeatherDetailView: View {
    let icon: String
    let title: String
    let value: String
    
    var body: some View {
        VStack {
            Image(systemName: icon)
                .font(.title2)
                .foregroundColor(.white)
            Text(title)
                .font(.caption)
                .foregroundColor(.white.opacity(0.8))
            Text(value)
                .font(.headline)
                .foregroundColor(.white)
        }
    }
}

// Weather Manager for Toronto data
class WeatherManager: ObservableObject {
    @Published var currentWeather: WeatherData?
    private let locationManager = CLLocationManager()
    
    struct WeatherData {
        let temperature: Int
        let description: String
        let humidity: Int
        let windSpeed: Int
        let feelsLike: Int
    }
    
    init() {
        setupLocationManager()
    }
    
    private func setupLocationManager() {
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
    }
    
    func requestLocation() {
        locationManager.requestWhenInUseAuthorization()
        locationManager.requestLocation()
    }
    
    func fetchWeatherData(for location: CLLocation) {
        // Integration with Environment Canada API
        let weatherAPIURL = "https://api.weather.gc.ca/forecast/Toronto"
        
        // Mock data for tutorial - in real app, use URLSession
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.currentWeather = WeatherData(
                temperature: -2,
                description: "Light Snow",
                humidity: 78,
                windSpeed: 15,
                feelsLike: -8
            )
        }
    }
    
    func checkTTCStatus() {
        // Integration with TTC API for service alerts
        // In real app, would fetch from TTC's real-time API
    }
}

extension WeatherManager: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let location = locations.first else { return }
        fetchWeatherData(for: location)
    }
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print("Failed to get location: \(error)")
    }
}

Hands-on Project:

Toronto Transit Tracker - Build a comprehensive iOS app that tracks TTC buses, subways, and GO Transit with real-time delays, route planning, and offline maps.

πŸ€– Module 2: Android Development with Kotlin

Beginner

Topics Covered:

  • Kotlin programming language
  • Android Studio and Jetpack Compose
  • Activities, Fragments, and ViewModels
  • Room database and data persistence

Code Example - Toronto Food Delivery App (Kotlin):

// Toronto Food Delivery App using Jetpack Compose
package com.torontofood.app

import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel

// Data models for Toronto restaurants
data class Restaurant(
    val id: String,
    val name: String,
    val cuisine: String,
    val neighborhood: TorontoNeighborhood,
    val rating: Float,
    val deliveryTime: Int,
    val deliveryFee: Double,
    val isOpen: Boolean,
    val specialties: List
)

enum class TorontoNeighborhood(val displayName: String) {
    DOWNTOWN("Downtown Toronto"),
    NORTH_YORK("North York"),
    SCARBOROUGH("Scarborough"),
    ETOBICOKE("Etobicoke"),
    EAST_YORK("East York"),
    YORKVILLE("Yorkville"),
    DISTILLERY("Distillery District"),
    LIBERTY_VILLAGE("Liberty Village"),
    KING_WEST("King West"),
    FINANCIAL_DISTRICT("Financial District")
}

// ViewModel for managing restaurant data
class TorontoFoodViewModel : ViewModel() {
    private val _restaurants = mutableStateOf(getSampleTorontoRestaurants())
    val restaurants: State> = _restaurants
    
    private val _selectedNeighborhood = mutableStateOf(null)
    val selectedNeighborhood: State = _selectedNeighborhood
    
    fun filterByNeighborhood(neighborhood: TorontoNeighborhood?) {
        _selectedNeighborhood.value = neighborhood
    }
    
    fun getFilteredRestaurants(): List {
        return if (_selectedNeighborhood.value == null) {
            _restaurants.value
        } else {
            _restaurants.value.filter { it.neighborhood == _selectedNeighborhood.value }
        }
    }
    
    private fun getSampleTorontoRestaurants(): List {
        return listOf(
            Restaurant(
                id = "1",
                name = "The Keg King St",
                cuisine = "Canadian Steakhouse",
                neighborhood = TorontoNeighborhood.FINANCIAL_DISTRICT,
                rating = 4.5f,
                deliveryTime = 35,
                deliveryFee = 3.99,
                isOpen = true,
                specialties = listOf("AAA Steaks", "Canadian Wine", "Poutine")
            ),
            Restaurant(
                id = "2",
                name = "Pai Northern Thai Kitchen",
                cuisine = "Thai",
                neighborhood = TorontoNeighborhood.DOWNTOWN,
                rating = 4.7f,
                deliveryTime = 25,
                deliveryFee = 2.99,
                isOpen = true,
                specialties = listOf("Pad Thai", "Green Curry", "Mango Sticky Rice")
            ),
            Restaurant(
                id = "3",
                name = "St. Lawrence Market Peameal",
                cuisine = "Canadian Deli",
                neighborhood = TorontoNeighborhood.DOWNTOWN,
                rating = 4.8f,
                deliveryTime = 20,
                deliveryFee = 1.99,
                isOpen = true,
                specialties = listOf("Peameal Bacon Sandwich", "Butter Tarts", "Tourtière")
            )
        )
    }
}

// Main Composable Screen
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TorontoFoodDeliveryScreen(
    viewModel: TorontoFoodViewModel = viewModel()
) {
    val restaurants = viewModel.getFilteredRestaurants()
    val selectedNeighborhood by viewModel.selectedNeighborhood
    
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
    ) {
        // Toronto-themed header
        Card(
            modifier = Modifier.fillMaxWidth(),
            colors = CardDefaults.cardColors(
                containerColor = Color(0xFF003366) // Toronto blue
            )
        ) {
            Column(
                modifier = Modifier.padding(16.dp),
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Text(
                    text = "🍁 Toronto Food Delivery",
                    style = MaterialTheme.typography.headlineMedium,
                    color = Color.White
                )
                Text(
                    text = "Discover the best of Toronto cuisine",
                    style = MaterialTheme.typography.bodyMedium,
                    color = Color.White.copy(alpha = 0.9f)
                )
            }
        }
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Neighborhood filter
        NeighborhoodFilter(
            selectedNeighborhood = selectedNeighborhood,
            onNeighborhoodSelected = { viewModel.filterByNeighborhood(it) }
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Restaurant list
        LazyColumn(
            verticalArrangement = Arrangement.spacedBy(12.dp)
        ) {
            items(restaurants) { restaurant ->
                RestaurantCard(restaurant = restaurant)
            }
        }
    }
}

@Composable
fun NeighborhoodFilter(
    selectedNeighborhood: TorontoNeighborhood?,
    onNeighborhoodSelected: (TorontoNeighborhood?) -> Unit
) {
    var expanded by remember { mutableStateOf(false) }
    
    ExposedDropdownMenuBox(
        expanded = expanded,
        onExpandedChange = { expanded = !expanded }
    ) {
        OutlinedTextField(
            value = selectedNeighborhood?.displayName ?: "All Toronto",
            onValueChange = { },
            readOnly = true,
            label = { Text("Filter by Neighborhood") },
            trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
            modifier = Modifier
                .menuAnchor()
                .fillMaxWidth()
        )
        
        ExposedDropdownMenu(
            expanded = expanded,
            onDismissRequest = { expanded = false }
        ) {
            DropdownMenuItem(
                text = { Text("All Toronto") },
                onClick = {
                    onNeighborhoodSelected(null)
                    expanded = false
                }
            )
            
            TorontoNeighborhood.values().forEach { neighborhood ->
                DropdownMenuItem(
                    text = { Text(neighborhood.displayName) },
                    onClick = {
                        onNeighborhoodSelected(neighborhood)
                        expanded = false
                    }
                )
            }
        }
    }
}

@Composable
fun RestaurantCard(restaurant: Restaurant) {
    Card(
        modifier = Modifier.fillMaxWidth(),
        elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
    ) {
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceBetween,
                verticalAlignment = Alignment.Top
            ) {
                Column(modifier = Modifier.weight(1f)) {
                    Text(
                        text = restaurant.name,
                        style = MaterialTheme.typography.titleMedium
                    )
                    Text(
                        text = restaurant.cuisine,
                        style = MaterialTheme.typography.bodyMedium,
                        color = MaterialTheme.colorScheme.onSurfaceVariant
                    )
                    Text(
                        text = restaurant.neighborhood.displayName,
                        style = MaterialTheme.typography.bodySmall,
                        color = Color(0xFF003366) // Toronto blue
                    )
                }
                
                Column(horizontalAlignment = Alignment.End) {
                    Row(verticalAlignment = Alignment.CenterVertically) {
                        Text(
                            text = "⭐ ${restaurant.rating}",
                            style = MaterialTheme.typography.bodyMedium
                        )
                    }
                    Text(
                        text = "${restaurant.deliveryTime} min β€’ $${restaurant.deliveryFee}",
                        style = MaterialTheme.typography.bodySmall,
                        color = MaterialTheme.colorScheme.onSurfaceVariant
                    )
                }
            }
            
            if (restaurant.specialties.isNotEmpty()) {
                Spacer(modifier = Modifier.height(8.dp))
                Text(
                    text = "Specialties: ${restaurant.specialties.joinToString(", ")}",
                    style = MaterialTheme.typography.bodySmall,
                    color = MaterialTheme.colorScheme.onSurfaceVariant
                )
            }
            
            Spacer(modifier = Modifier.height(12.dp))
            
            Button(
                onClick = { /* Navigate to restaurant details */ },
                modifier = Modifier.fillMaxWidth(),
                colors = ButtonDefaults.buttonColors(
                    containerColor = if (restaurant.isOpen) Color(0xFFD32F2F) else Color.Gray
                )
            ) {
                Text(
                    text = if (restaurant.isOpen) "Order Now" else "Currently Closed"
                )
            }
        }
    }
}

Hands-on Project:

Toronto Events Discovery App - Create an Android app that aggregates events from across Toronto, integrates with Ticketmaster, and provides location-based recommendations.

βš›οΈ Module 3: Cross-Platform with React Native

Intermediate

Topics Covered:

  • React Native fundamentals and setup
  • Navigation and state management
  • Native modules and platform-specific code
  • Performance optimization and debugging

Code Example - Toronto Real Estate App (React Native):

// Toronto Real Estate App - React Native
import React, { useState, useEffect } from 'react';
import {
  View,
  Text,
  FlatList,
  TouchableOpacity,
  StyleSheet,
  Image,
  Alert,
  Platform
} from 'react-native';
import MapView, { Marker } from 'react-native-maps';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

// Toronto Real Estate Data Model
const torontoNeighborhoods = [
  {
    id: '1',
    name: 'Downtown Toronto',
    averagePrice: 895000,
    pricePerSqFt: 1250,
    coordinates: { latitude: 43.6532, longitude: -79.3832 },
    trending: 'up',
    properties: [
      {
        id: '101',
        address: '123 King St W',
        price: 750000,
        bedrooms: 1,
        bathrooms: 1,
        sqft: 600,
        type: 'Condo',
        images: ['https://example.com/condo1.jpg'],
        description: 'Modern downtown condo with CN Tower views'
      },
      {
        id: '102',
        address: '456 Queen St E',
        price: 1200000,
        bedrooms: 2,
        bathrooms: 2,
        sqft: 950,
        type: 'Condo',
        images: ['https://example.com/condo2.jpg'],
        description: 'Luxury condo in the heart of Toronto'
      }
    ]
  },
  {
    id: '2',
    name: 'North York',
    averagePrice: 725000,
    pricePerSqFt: 850,
    coordinates: { latitude: 43.7615, longitude: -79.4111 },
    trending: 'stable',
    properties: [
      {
        id: '201',
        address: '789 Yonge St',
        price: 680000,
        bedrooms: 3,
        bathrooms: 2,
        sqft: 1200,
        type: 'Townhouse',
        images: ['https://example.com/townhouse1.jpg'],
        description: 'Family-friendly townhouse near TTC'
      }
    ]
  }
];

// Main App Component
const TorontoRealEstateApp = () => {
  const Stack = createStackNavigator();

  return (
    
      
        
         ({ title: route.params.neighborhoodName })}
        />
        
        
      
    
  );
};

// Neighborhood List Screen
const NeighborhoodListScreen = ({ navigation }) => {
  const [neighborhoods, setNeighborhoods] = useState(torontoNeighborhoods);

  const renderNeighborhood = ({ item }) => (
     navigation.navigate('PropertyList', { 
        neighborhood: item,
        neighborhoodName: item.name 
      })}
    >
      
        {item.name}
        
          
            {item.trending === 'up' ? 'πŸ“ˆ' : 'πŸ“Š'} {item.trending.toUpperCase()}
          
        
      
      
      
        
          Average: ${item.averagePrice.toLocaleString()}
        
        
          ${item.pricePerSqFt}/sq ft
        
      
      
      
        {item.properties.length} properties available
      
    
  );

  return (
    
      
        Toronto Neighborhoods
         navigation.navigate('MapView')}
        >
          πŸ—ΊοΈ Map View
        
      
      
       item.id}
        showsVerticalScrollIndicator={false}
      />
    
  );
};

// Property List Screen
const PropertyListScreen = ({ route, navigation }) => {
  const { neighborhood } = route.params;
  const [properties, setProperties] = useState(neighborhood.properties);

  const renderProperty = ({ item }) => (
     navigation.navigate('PropertyDetails', { property: item })}
    >
      
        🏠
      
      
      
        {item.address}
        
          ${item.price.toLocaleString()}
        
        
        
          
            πŸ›οΈ {item.bedrooms} bed
          
          
            🚿 {item.bathrooms} bath
          
          
            πŸ“ {item.sqft} sq ft
          
        
        
        {item.type}
      
    
  );

  return (
    
      
        
          Properties in {neighborhood.name}
        
        
          Average: ${neighborhood.averagePrice.toLocaleString()} β€’ 
          ${neighborhood.pricePerSqFt}/sq ft
        
      
      
       item.id}
        showsVerticalScrollIndicator={false}
      />
    
  );
};

// Toronto Map View Screen
const MapViewScreen = () => {
  const [selectedProperty, setSelectedProperty] = useState(null);

  const allProperties = torontoNeighborhoods.flatMap(neighborhood => 
    neighborhood.properties.map(property => ({
      ...property,
      neighborhood: neighborhood.name,
      coordinates: neighborhood.coordinates
    }))
  );

  return (
    
      
        {torontoNeighborhoods.map(neighborhood => (
          
        ))}
      
      
      {selectedProperty && (
        
          
            {selectedProperty.address}
          
          
            ${selectedProperty.price.toLocaleString()}
          
        
      )}
    
  );
};

// Property Details Screen
const PropertyDetailsScreen = ({ route }) => {
  const { property } = route.params;

  const handleContactAgent = () => {
    Alert.alert(
      'Contact Agent',
      'Would you like to contact the listing agent?',
      [
        { text: 'Cancel', style: 'cancel' },
        { text: 'Call', onPress: () => console.log('Calling agent...') },
        { text: 'Email', onPress: () => console.log('Emailing agent...') }
      ]
    );
  };

  return (
    
      
        {property.address}
        
          ${property.price.toLocaleString()}
        
      
      
      
        
          Bedrooms
          {property.bedrooms}
        
        
          Bathrooms
          {property.bathrooms}
        
        
          Square Feet
          {property.sqft}
        
        
          Property Type
          {property.type}
        
      
      
      
        Description
        {property.description}
      
      
      
        Contact Agent
      
    
  );
};

// Helper Functions
const getTrendingColor = (trending) => {
  switch (trending) {
    case 'up': return '#4CAF50';
    case 'down': return '#F44336';
    default: return '#FF9800';
  }
};

// Styles
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  header: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: 16,
    backgroundColor: '#fff',
    borderBottomWidth: 1,
    borderBottomColor: '#e0e0e0',
  },
  headerTitle: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#003366',
  },
  mapButton: {
    backgroundColor: '#003366',
    padding: 8,
    borderRadius: 6,
  },
  mapButtonText: {
    color: '#fff',
    fontWeight: 'bold',
  },
  neighborhoodCard: {
    backgroundColor: '#fff',
    margin: 12,
    padding: 16,
    borderRadius: 8,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  cardHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 8,
  },
  neighborhoodName: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#003366',
  },
  trendingIndicator: {
    paddingHorizontal: 8,
    paddingVertical: 4,
    borderRadius: 4,
  },
  trendingText: {
    color: '#fff',
    fontSize: 12,
    fontWeight: 'bold',
  },
  priceInfo: {
    marginBottom: 8,
  },
  averagePrice: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#333',
  },
  pricePerSqFt: {
    fontSize: 14,
    color: '#666',
  },
  propertyCount: {
    fontSize: 14,
    color: '#666',
    fontStyle: 'italic',
  },
  mapContainer: {
    flex: 1,
  },
  map: {
    flex: 1,
  },
  // Additional styles...
});

export default TorontoRealEstateApp;

Hands-on Project:

Toronto Fitness Community App - Build a React Native app connecting fitness enthusiasts across Toronto with gym finder, workout tracking, and social features.

πŸ¦‹ Module 4: Cross-Platform with Flutter

Intermediate

Topics Covered:

  • Dart programming language
  • Flutter widgets and layouts
  • State management with Provider/Riverpod
  • Platform channels and native integration

Code Example - Toronto Bike Share App (Flutter):

// Toronto Bike Share App - Flutter
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:location/location.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

void main() {
  runApp(TorontoBikeShareApp());
}

class TorontoBikeShareApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Toronto Bike Share',
      theme: ThemeData(
        primaryColor: Color(0xFF003366), // Toronto blue
        accentColor: Color(0xFFD32F2F), // Toronto red
        fontFamily: 'Roboto',
      ),
      home: BikeShareHomeScreen(),
    );
  }
}

// Data Models
class BikeStation {
  final String id;
  final String name;
  final double latitude;
  final double longitude;
  final int availableBikes;
  final int availableDocks;
  final bool isActive;
  final String address;
  final String neighborhood;

  BikeStation({
    required this.id,
    required this.name,
    required this.latitude,
    required this.longitude,
    required this.availableBikes,
    required this.availableDocks,
    required this.isActive,
    required this.address,
    required this.neighborhood,
  });

  factory BikeStation.fromJson(Map json) {
    return BikeStation(
      id: json['station_id'],
      name: json['name'],
      latitude: json['lat'].toDouble(),
      longitude: json['lon'].toDouble(),
      availableBikes: json['num_bikes_available'],
      availableDocks: json['num_docks_available'],
      isActive: json['is_active'] == 1,
      address: json['address'] ?? '',
      neighborhood: json['neighborhood'] ?? 'Toronto',
    );
  }
}

class BikeTrip {
  final String id;
  final BikeStation startStation;
  final BikeStation? endStation;
  final DateTime startTime;
  final DateTime? endTime;
  final double? distance;
  final int? duration;

  BikeTrip({
    required this.id,
    required this.startStation,
    this.endStation,
    required this.startTime,
    this.endTime,
    this.distance,
    this.duration,
  });
}

// Main Home Screen
class BikeShareHomeScreen extends StatefulWidget {
  @override
  _BikeShareHomeScreenState createState() => _BikeShareHomeScreenState();
}

class _BikeShareHomeScreenState extends State
    with SingleTickerProviderStateMixin {
  late TabController _tabController;
  List _stations = [];
  BikeStation? _nearestStation;
  LocationData? _currentLocation;
  bool _isLoading = true;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 3, vsync: this);
    _loadBikeStations();
    _getCurrentLocation();
  }

  Future _loadBikeStations() async {
    try {
      // In real app, would use Toronto Bike Share API
      // For demo, using sample data
      setState(() {
        _stations = _getSampleTorontoStations();
        _isLoading = false;
      });
    } catch (e) {
      print('Error loading stations: $e');
      setState(() {
        _isLoading = false;
      });
    }
  }

  Future _getCurrentLocation() async {
    Location location = Location();
    
    bool serviceEnabled = await location.serviceEnabled();
    if (!serviceEnabled) {
      serviceEnabled = await location.requestService();
      if (!serviceEnabled) return;
    }

    PermissionStatus permissionGranted = await location.hasPermission();
    if (permissionGranted == PermissionStatus.denied) {
      permissionGranted = await location.requestPermission();
      if (permissionGranted != PermissionStatus.granted) return;
    }

    LocationData locationData = await location.getLocation();
    setState(() {
      _currentLocation = locationData;
      _findNearestStation();
    });
  }

  void _findNearestStation() {
    if (_currentLocation == null || _stations.isEmpty) return;

    double minDistance = double.infinity;
    BikeStation? nearest;

    for (BikeStation station in _stations) {
      double distance = _calculateDistance(
        _currentLocation!.latitude!,
        _currentLocation!.longitude!,
        station.latitude,
        station.longitude,
      );

      if (distance < minDistance && station.isActive && station.availableBikes > 0) {
        minDistance = distance;
        nearest = station;
      }
    }

    setState(() {
      _nearestStation = nearest;
    });
  }

  double _calculateDistance(double lat1, double lon1, double lat2, double lon2) {
    // Haversine formula for distance calculation
    const double earthRadius = 6371; // Earth's radius in kilometers
    double dLat = _degreeToRadian(lat2 - lat1);
    double dLon = _degreeToRadian(lon2 - lon1);
    
    double a = math.sin(dLat / 2) * math.sin(dLat / 2) +
        math.cos(_degreeToRadian(lat1)) * math.cos(_degreeToRadian(lat2)) *
        math.sin(dLon / 2) * math.sin(dLon / 2);
    
    double c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a));
    return earthRadius * c;
  }

  double _degreeToRadian(double degree) {
    return degree * (math.pi / 180);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Row(
          children: [
            Icon(Icons.directions_bike, color: Colors.white),
            SizedBox(width: 8),
            Text('Toronto Bike Share', 
                 style: TextStyle(fontWeight: FontWeight.bold)),
          ],
        ),
        backgroundColor: Theme.of(context).primaryColor,
        bottom: TabBar(
          controller: _tabController,
          tabs: [
            Tab(icon: Icon(Icons.map), text: 'Map'),
            Tab(icon: Icon(Icons.list), text: 'Stations'),
            Tab(icon: Icon(Icons.history), text: 'My Trips'),
          ],
        ),
      ),
      body: _isLoading
          ? Center(child: CircularProgressIndicator())
          : TabBarView(
              controller: _tabController,
              children: [
                BikeStationMapView(stations: _stations, currentLocation: _currentLocation),
                BikeStationListView(stations: _stations, nearestStation: _nearestStation),
                MyTripsView(),
              ],
            ),
      floatingActionButton: _nearestStation != null
          ? FloatingActionButton.extended(
              onPressed: () => _startBikeRental(_nearestStation!),
              icon: Icon(Icons.play_arrow),
              label: Text('Start Ride'),
              backgroundColor: Theme.of(context).accentColor,
            )
          : null,
    );
  }

  void _startBikeRental(BikeStation station) {
    showModalBottomSheet(
      context: context,
      builder: (context) => BikeRentalSheet(station: station),
    );
  }

  List _getSampleTorontoStations() {
    return [
      BikeStation(
        id: '1',
        name: 'Union Station',
        latitude: 43.6452,
        longitude: -79.3806,
        availableBikes: 12,
        availableDocks: 8,
        isActive: true,
        address: '65 Front St W, Toronto',
        neighborhood: 'Financial District',
      ),
      BikeStation(
        id: '2',
        name: 'Harbourfront Centre',
        latitude: 43.6387,
        longitude: -79.3816,
        availableBikes: 8,
        availableDocks: 12,
        isActive: true,
        address: '235 Queens Quay W, Toronto',
        neighborhood: 'Harbourfront',
      ),
      BikeStation(
        id: '3',
        name: 'CN Tower',
        latitude: 43.6426,
        longitude: -79.3871,
        availableBikes: 15,
        availableDocks: 5,
        isActive: true,
        address: '290 Bremner Blvd, Toronto',
        neighborhood: 'Entertainment District',
      ),
      // Add more sample stations...
    ];
  }
}

// Bike Station List View
class BikeStationListView extends StatelessWidget {
  final List stations;
  final BikeStation? nearestStation;

  const BikeStationListView({
    Key? key,
    required this.stations,
    this.nearestStation,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      padding: EdgeInsets.all(8),
      itemCount: stations.length,
      itemBuilder: (context, index) {
        BikeStation station = stations[index];
        bool isNearest = nearestStation?.id == station.id;

        return Card(
          elevation: isNearest ? 4 : 2,
          margin: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
          child: ListTile(
            leading: CircleAvatar(
              backgroundColor: station.isActive 
                  ? (station.availableBikes > 0 ? Colors.green : Colors.orange)
                  : Colors.grey,
              child: Text(
                '${station.availableBikes}',
                style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
              ),
            ),
            title: Row(
              children: [
                Expanded(
                  child: Text(
                    station.name,
                    style: TextStyle(
                      fontWeight: isNearest ? FontWeight.bold : FontWeight.normal,
                    ),
                  ),
                ),
                if (isNearest)
                  Container(
                    padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                    decoration: BoxDecoration(
                      color: Colors.blue,
                      borderRadius: BorderRadius.circular(12),
                    ),
                    child: Text(
                      'NEAREST',
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 12,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  ),
              ],
            ),
            subtitle: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(station.address),
                SizedBox(height: 4),
                Row(
                  children: [
                    Icon(Icons.directions_bike, size: 16, color: Colors.green),
                    Text(' ${station.availableBikes} bikes'),
                    SizedBox(width: 16),
                    Icon(Icons.lock_open, size: 16, color: Colors.blue),
                    Text(' ${station.availableDocks} docks'),
                  ],
                ),
              ],
            ),
            trailing: Icon(Icons.arrow_forward_ios),
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => StationDetailScreen(station: station),
                ),
              );
            },
          ),
        );
      },
    );
  }
}

// Bike Rental Bottom Sheet
class BikeRentalSheet extends StatefulWidget {
  final BikeStation station;

  const BikeRentalSheet({Key? key, required this.station}) : super(key: key);

  @override
  _BikeRentalSheetState createState() => _BikeRentalSheetState();
}

class _BikeRentalSheetState extends State {
  bool _isRenting = false;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(20),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Text(
            'Rent a Bike',
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          SizedBox(height: 16),
          Card(
            child: Padding(
              padding: EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    widget.station.name,
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                  ),
                  SizedBox(height: 8),
                  Text(widget.station.address),
                  SizedBox(height: 8),
                  Row(
                    children: [
                      Icon(Icons.directions_bike, color: Colors.green),
                      Text(' ${widget.station.availableBikes} bikes available'),
                    ],
                  ),
                ],
              ),
            ),
          ),
          SizedBox(height: 20),
          SizedBox(
            width: double.infinity,
            child: ElevatedButton(
              onPressed: _isRenting ? null : _startRental,
              style: ElevatedButton.styleFrom(
                backgroundColor: Theme.of(context).accentColor,
                padding: EdgeInsets.symmetric(vertical: 16),
              ),
              child: _isRenting
                  ? CircularProgressIndicator(color: Colors.white)
                  : Text(
                      'Start Rental - \$1.15 + \$0.12/min',
                      style: TextStyle(fontSize: 16),
                    ),
            ),
          ),
          SizedBox(height: 16),
        ],
      ),
    );
  }

  Future _startRental() async {
    setState(() {
      _isRenting = true;
    });

    // Simulate rental process
    await Future.delayed(Duration(seconds: 2));

    setState(() {
      _isRenting = false;
    });

    Navigator.pop(context);
    
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('Bike rental started! Enjoy your ride in Toronto!'),
        backgroundColor: Colors.green,
      ),
    );
  }
}

// Add math import at the top
import 'dart:math' as math;

Hands-on Project:

Toronto Language Exchange App - Create a Flutter app connecting language learners across Toronto's multicultural communities with chat, meetup organization, and progress tracking.

Mobile Development Career Paths in Toronto

🎯 Mobile Developer Roles in Toronto

  • iOS Developer ($85-140k CAD): Shopify, RBC Mobile, TD App
  • Android Developer ($80-135k CAD): Wealthsimple, FreshBooks
  • React Native Developer ($90-150k CAD): Startups, E-commerce
  • Flutter Developer ($85-145k CAD): Cross-platform teams
  • Mobile UI/UX Designer ($75-120k CAD): Design-focused roles

πŸš€ Toronto Mobile App Market

  • Fintech: Banking, payments, cryptocurrency apps
  • E-commerce: Retail, food delivery, marketplace apps
  • Health Tech: Telemedicine, fitness, mental health
  • Transportation: TTC, ride-sharing, logistics
  • Government: Service Ontario, municipal services

Building Your Mobile Development Portfolio

App Store Presence: Publish 2-3 polished apps on both iOS App Store and Google Play Store showcasing different technologies and Toronto market needs.

Portfolio App Ideas for Toronto Market:

  1. Toronto Transit Companion - Real-time TTC tracking with offline maps
  2. Local Business Discovery - Support Toronto small businesses post-COVID
  3. Toronto Events Hub - Aggregate events from venues across the city
  4. Canadian Tax Calculator - Personal finance app for Canadian tax system
  5. Toronto Food Scene - Restaurant discovery with Canadian dietary preferences

Technical Skills Showcase: Demonstrate API integration, offline functionality, push notifications, payment processing, and accessibility features in your apps.

Mobile development career opportunities in Toronto

Resources & Development Tools

πŸ› οΈ Development Tools

  • Xcode (iOS development)
  • Android Studio (Android development)
  • VS Code with extensions
  • React Native CLI / Expo
  • Flutter SDK and Dart

πŸ“š Learning Resources

  • Apple Developer Documentation
  • Android Developer Guides
  • React Native Documentation
  • Flutter.dev tutorials
  • Ray Wenderlich tutorials

πŸŽ“ Toronto Mobile Communities

  • Toronto iOS Developers
  • Android Developers Toronto
  • React Native Toronto
  • Flutter Toronto Meetup
  • Mobile UX Toronto

Ready to Build Amazing Mobile Apps?

Join Toronto's thriving mobile development community and create apps that serve millions of Canadian users.

Get Mobile Mentorship Explore All Tutorials