Skip to content

移动开发专家 (Mobile Developer)

移动开发专家是 Claude Code 的移动应用开发专家,精通 iOS、Android 和跨平台移动应用开发。

核心职责

主要能力

  • 跨平台开发: React Native、Flutter、Xamarin 应用开发
  • 原生开发: iOS (Swift/Objective-C) 和 Android (Kotlin/Java)
  • UI/UX 实现: 移动端界面设计和用户体验优化
  • 性能优化: 内存管理、电池优化、网络优化

专业领域

  • 移动应用架构设计
  • 原生功能集成
  • 应用商店发布
  • 移动端安全实现

使用场景

何时使用移动开发专家

适合的场景

bash
# 跨平台应用开发
"使用 React Native 开发电商应用"

# 原生功能集成
"集成相机、GPS 和推送通知功能"

# 性能优化
"优化 Flutter 应用启动速度和内存使用"

# 应用发布
"准备 iOS App Store 和 Google Play 发布"

不适合的场景

bash
# 后端开发 (应使用执行器)
"开发 REST API 服务"

# Web 开发 (应使用前端专家)
"创建响应式网站"

# 数据分析 (应使用数据分析师)
"分析用户行为数据"

移动开发能力

1. React Native 开发

项目架构设计

typescript
// App.tsx - 主应用组件
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { StatusBar } from 'expo-status-bar';

import { store, persistor } from './src/store/store';
import AuthNavigator from './src/navigation/AuthNavigator';
import MainNavigator from './src/navigation/MainNavigator';
import LoadingScreen from './src/components/LoadingScreen';
import { useAppSelector } from './src/hooks/redux';

const Stack = createNativeStackNavigator();

function AppContent() {
  const { isAuthenticated, isLoading } = useAppSelector(state => state.auth);

  if (isLoading) {
    return <LoadingScreen />;
  }

  return (
    <NavigationContainer>
      <StatusBar style="auto" />
      {isAuthenticated ? <MainNavigator /> : <AuthNavigator />}
    </NavigationContainer>
  );
}

export default function App() {
  return (
    <Provider store={store}>
      <PersistGate loading={<LoadingScreen />} persistor={persistor}>
        <AppContent />
      </PersistGate>
    </Provider>
  );
}

状态管理 (Redux Toolkit)

typescript
// store/store.ts
import { configureStore } from '@reduxjs/toolkit';
import { persistStore, persistReducer } from 'redux-persist';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { combineReducers } from '@reduxjs/toolkit';

import authSlice from './slices/authSlice';
import userSlice from './slices/userSlice';
import productsSlice from './slices/productsSlice';
import cartSlice from './slices/cartSlice';

const persistConfig = {
  key: 'root',
  storage: AsyncStorage,
  whitelist: ['auth', 'cart'], // 只持久化这些状态
};

const rootReducer = combineReducers({
  auth: authSlice,
  user: userSlice,
  products: productsSlice,
  cart: cartSlice,
});

const persistedReducer = persistReducer(persistConfig, rootReducer);

export const store = configureStore({
  reducer: persistedReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE'],
      },
    }),
});

export const persistor = persistStore(store);

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

// store/slices/authSlice.ts
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import { authAPI } from '../../services/api';

interface User {
  id: string;
  email: string;
  name: string;
  avatar?: string;
}

interface AuthState {
  user: User | null;
  token: string | null;
  isAuthenticated: boolean;
  isLoading: boolean;
  error: string | null;
}

const initialState: AuthState = {
  user: null,
  token: null,
  isAuthenticated: false,
  isLoading: false,
  error: null,
};

// 异步操作
export const loginUser = createAsyncThunk(
  'auth/loginUser',
  async (credentials: { email: string; password: string }, { rejectWithValue }) => {
    try {
      const response = await authAPI.login(credentials);
      return response.data;
    } catch (error: any) {
      return rejectWithValue(error.response?.data?.message || '登录失败');
    }
  }
);

export const registerUser = createAsyncThunk(
  'auth/registerUser',
  async (userData: { email: string; password: string; name: string }, { rejectWithValue }) => {
    try {
      const response = await authAPI.register(userData);
      return response.data;
    } catch (error: any) {
      return rejectWithValue(error.response?.data?.message || '注册失败');
    }
  }
);

const authSlice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    logout: (state) => {
      state.user = null;
      state.token = null;
      state.isAuthenticated = false;
      state.error = null;
    },
    clearError: (state) => {
      state.error = null;
    },
  },
  extraReducers: (builder) => {
    builder
      // 登录
      .addCase(loginUser.pending, (state) => {
        state.isLoading = true;
        state.error = null;
      })
      .addCase(loginUser.fulfilled, (state, action) => {
        state.isLoading = false;
        state.user = action.payload.user;
        state.token = action.payload.token;
        state.isAuthenticated = true;
      })
      .addCase(loginUser.rejected, (state, action) => {
        state.isLoading = false;
        state.error = action.payload as string;
      })
      // 注册
      .addCase(registerUser.pending, (state) => {
        state.isLoading = true;
        state.error = null;
      })
      .addCase(registerUser.fulfilled, (state, action) => {
        state.isLoading = false;
        state.user = action.payload.user;
        state.token = action.payload.token;
        state.isAuthenticated = true;
      })
      .addCase(registerUser.rejected, (state, action) => {
        state.isLoading = false;
        state.error = action.payload as string;
      });
  },
});

export const { logout, clearError } = authSlice.actions;
export default authSlice.reducer;

自定义组件和 Hooks

typescript
// components/CustomButton.tsx
import React from 'react';
import { 
  TouchableOpacity, 
  Text, 
  StyleSheet, 
  ViewStyle, 
  TextStyle,
  ActivityIndicator
} from 'react-native';
import { useTheme } from '../contexts/ThemeContext';

interface CustomButtonProps {
  title: string;
  onPress: () => void;
  variant?: 'primary' | 'secondary' | 'outline';
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
  loading?: boolean;
  style?: ViewStyle;
  textStyle?: TextStyle;
}

export const CustomButton: React.FC<CustomButtonProps> = ({
  title,
  onPress,
  variant = 'primary',
  size = 'medium',
  disabled = false,
  loading = false,
  style,
  textStyle,
}) => {
  const { theme } = useTheme();

  const getButtonStyle = (): ViewStyle => {
    const baseStyle: ViewStyle = {
      borderRadius: 8,
      alignItems: 'center',
      justifyContent: 'center',
      flexDirection: 'row',
    };

    // 尺寸样式
    const sizeStyles = {
      small: { paddingVertical: 8, paddingHorizontal: 16 },
      medium: { paddingVertical: 12, paddingHorizontal: 24 },
      large: { paddingVertical: 16, paddingHorizontal: 32 },
    };

    // 变体样式
    const variantStyles = {
      primary: {
        backgroundColor: disabled ? theme.colors.disabled : theme.colors.primary,
      },
      secondary: {
        backgroundColor: disabled ? theme.colors.disabled : theme.colors.secondary,
      },
      outline: {
        backgroundColor: 'transparent',
        borderWidth: 1,
        borderColor: disabled ? theme.colors.disabled : theme.colors.primary,
      },
    };

    return {
      ...baseStyle,
      ...sizeStyles[size],
      ...variantStyles[variant],
    };
  };

  const getTextStyle = (): TextStyle => {
    const baseStyle: TextStyle = {
      fontWeight: '600',
      fontSize: size === 'small' ? 14 : size === 'medium' ? 16 : 18,
    };

    const variantTextStyles = {
      primary: { color: theme.colors.onPrimary },
      secondary: { color: theme.colors.onSecondary },
      outline: { color: disabled ? theme.colors.disabled : theme.colors.primary },
    };

    return {
      ...baseStyle,
      ...variantTextStyles[variant],
    };
  };

  return (
    <TouchableOpacity
      style={[getButtonStyle(), style]}
      onPress={onPress}
      disabled={disabled || loading}
      activeOpacity={0.7}
    >
      {loading ? (
        <ActivityIndicator 
          size="small" 
          color={variant === 'outline' ? theme.colors.primary : theme.colors.onPrimary} 
        />
      ) : (
        <Text style={[getTextStyle(), textStyle]}>{title}</Text>
      )}
    </TouchableOpacity>
  );
};

// hooks/useApi.ts
import { useState, useEffect } from 'react';
import { useAppSelector } from './redux';

interface ApiState<T> {
  data: T | null;
  loading: boolean;
  error: string | null;
}

export function useApi<T>(
  apiCall: () => Promise<T>,
  dependencies: any[] = []
): ApiState<T> & { refetch: () => Promise<void> } {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);

  const fetchData = async () => {
    try {
      setLoading(true);
      setError(null);
      const result = await apiCall();
      setData(result);
    } catch (err: any) {
      setError(err.message || '请求失败');
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchData();
  }, dependencies);

  return {
    data,
    loading,
    error,
    refetch: fetchData,
  };
}

// hooks/useNetworkStatus.ts
import { useState, useEffect } from 'react';
import NetInfo from '@react-native-netinfo/netinfo';

export const useNetworkStatus = () => {
  const [isConnected, setIsConnected] = useState<boolean | null>(null);
  const [connectionType, setConnectionType] = useState<string | null>(null);

  useEffect(() => {
    const unsubscribe = NetInfo.addEventListener(state => {
      setIsConnected(state.isConnected);
      setConnectionType(state.type);
    });

    return unsubscribe;
  }, []);

  return { isConnected, connectionType };
};

原生功能集成

typescript
// services/CameraService.ts
import { launchCamera, launchImageLibrary, MediaType } from 'react-native-image-picker';
import { request, PERMISSIONS, RESULTS } from 'react-native-permissions';
import { Platform } from 'react-native';

export class CameraService {
  static async requestCameraPermission(): Promise<boolean> {
    try {
      const permission = Platform.OS === 'ios' 
        ? PERMISSIONS.IOS.CAMERA 
        : PERMISSIONS.ANDROID.CAMERA;
      
      const result = await request(permission);
      return result === RESULTS.GRANTED;
    } catch (error) {
      console.error('相机权限请求失败:', error);
      return false;
    }
  }

  static async takePhoto(): Promise<string | null> {
    const hasPermission = await this.requestCameraPermission();
    if (!hasPermission) {
      throw new Error('相机权限被拒绝');
    }

    return new Promise((resolve, reject) => {
      launchCamera(
        {
          mediaType: 'photo' as MediaType,
          quality: 0.8,
          maxWidth: 1024,
          maxHeight: 1024,
        },
        (response) => {
          if (response.didCancel) {
            resolve(null);
          } else if (response.errorMessage) {
            reject(new Error(response.errorMessage));
          } else if (response.assets && response.assets[0]) {
            resolve(response.assets[0].uri || null);
          } else {
            resolve(null);
          }
        }
      );
    });
  }

  static async pickFromLibrary(): Promise<string | null> {
    return new Promise((resolve, reject) => {
      launchImageLibrary(
        {
          mediaType: 'photo' as MediaType,
          quality: 0.8,
          maxWidth: 1024,
          maxHeight: 1024,
        },
        (response) => {
          if (response.didCancel) {
            resolve(null);
          } else if (response.errorMessage) {
            reject(new Error(response.errorMessage));
          } else if (response.assets && response.assets[0]) {
            resolve(response.assets[0].uri || null);
          } else {
            resolve(null);
          }
        }
      );
    });
  }
}

// services/LocationService.ts
import Geolocation from '@react-native-community/geolocation';
import { request, PERMISSIONS, RESULTS } from 'react-native-permissions';
import { Platform } from 'react-native';

interface Location {
  latitude: number;
  longitude: number;
  accuracy: number;
}

export class LocationService {
  static async requestLocationPermission(): Promise<boolean> {
    try {
      const permission = Platform.OS === 'ios'
        ? PERMISSIONS.IOS.LOCATION_WHEN_IN_USE
        : PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION;
      
      const result = await request(permission);
      return result === RESULTS.GRANTED;
    } catch (error) {
      console.error('位置权限请求失败:', error);
      return false;
    }
  }

  static async getCurrentLocation(): Promise<Location> {
    const hasPermission = await this.requestLocationPermission();
    if (!hasPermission) {
      throw new Error('位置权限被拒绝');
    }

    return new Promise((resolve, reject) => {
      Geolocation.getCurrentPosition(
        (position) => {
          resolve({
            latitude: position.coords.latitude,
            longitude: position.coords.longitude,
            accuracy: position.coords.accuracy,
          });
        },
        (error) => {
          reject(new Error(`获取位置失败: ${error.message}`));
        },
        {
          enableHighAccuracy: true,
          timeout: 15000,
          maximumAge: 10000,
        }
      );
    });
  }

  static watchLocation(callback: (location: Location) => void): number {
    return Geolocation.watchPosition(
      (position) => {
        callback({
          latitude: position.coords.latitude,
          longitude: position.coords.longitude,
          accuracy: position.coords.accuracy,
        });
      },
      (error) => {
        console.error('位置监听错误:', error);
      },
      {
        enableHighAccuracy: true,
        distanceFilter: 10,
      }
    );
  }

  static clearWatch(watchId: number): void {
    Geolocation.clearWatch(watchId);
  }
}

// services/NotificationService.ts
import PushNotification from 'react-native-push-notification';
import PushNotificationIOS from '@react-native-community/push-notification-ios';
import { Platform } from 'react-native';

export class NotificationService {
  static configure() {
    PushNotification.configure({
      onRegister: function (token) {
        console.log('推送令牌:', token);
        // 发送令牌到服务器
      },

      onNotification: function (notification) {
        console.log('收到通知:', notification);
        
        if (Platform.OS === 'ios') {
          notification.finish(PushNotificationIOS.FetchResult.NoData);
        }
      },

      onAction: function (notification) {
        console.log('通知动作:', notification.action);
      },

      onRegistrationError: function(err) {
        console.error('推送注册错误:', err.message);
      },

      permissions: {
        alert: true,
        badge: true,
        sound: true,
      },

      popInitialNotification: true,
      requestPermissions: Platform.OS === 'ios',
    });

    // 创建默认频道 (Android)
    if (Platform.OS === 'android') {
      PushNotification.createChannel(
        {
          channelId: 'default-channel',
          channelName: '默认通知',
          channelDescription: '应用默认通知频道',
          soundName: 'default',
          importance: 4,
          vibrate: true,
        },
        (created) => console.log(`通知频道创建: ${created}`)
      );
    }
  }

  static localNotification(title: string, message: string, data?: any) {
    PushNotification.localNotification({
      title,
      message,
      data,
      channelId: 'default-channel',
    });
  }

  static scheduleNotification(title: string, message: string, date: Date) {
    PushNotification.localNotificationSchedule({
      title,
      message,
      date,
      channelId: 'default-channel',
    });
  }

  static cancelAllNotifications() {
    PushNotification.cancelAllLocalNotifications();
  }

  static setBadgeCount(count: number) {
    if (Platform.OS === 'ios') {
      PushNotificationIOS.setApplicationIconBadgeNumber(count);
    }
  }
}

2. Flutter 开发

应用架构 (BLoC Pattern)

dart
// main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:path_provider/path_provider.dart';

import 'app/app.dart';
import 'app/app_bloc_observer.dart';
import 'repositories/repositories.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // 配置 HydratedBloc 存储
  HydratedBloc.storage = await HydratedStorage.build(
    storageDirectory: await getApplicationDocumentsDirectory(),
  );
  
  // 设置 BLoC 观察者
  Bloc.observer = AppBlocObserver();
  
  // 初始化仓库
  final authRepository = AuthRepository();
  final userRepository = UserRepository();
  final productsRepository = ProductsRepository();
  
  runApp(
    MultiRepositoryProvider(
      providers: [
        RepositoryProvider.value(value: authRepository),
        RepositoryProvider.value(value: userRepository),
        RepositoryProvider.value(value: productsRepository),
      ],
      child: App(),
    ),
  );
}

// app/app.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import '../auth/bloc/auth_bloc.dart';
import '../home/view/home_page.dart';
import '../login/view/login_page.dart';
import '../repositories/repositories.dart';
import '../theme/app_theme.dart';

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => AuthBloc(
        authRepository: context.read<AuthRepository>(),
      )..add(AuthStatusRequested()),
      child: AppView(),
    );
  }
}

class AppView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: AppTheme.lightTheme,
      darkTheme: AppTheme.darkTheme,
      home: BlocBuilder<AuthBloc, AuthState>(
        builder: (context, state) {
          switch (state.status) {
            case AuthStatus.authenticated:
              return HomePage();
            case AuthStatus.unauthenticated:
              return LoginPage();
            case AuthStatus.unknown:
            default:
              return SplashPage();
          }
        },
      ),
    );
  }
}

// auth/bloc/auth_bloc.dart
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';

import '../../models/models.dart';
import '../../repositories/repositories.dart';

part 'auth_event.dart';
part 'auth_state.dart';

class AuthBloc extends HydratedBloc<AuthEvent, AuthState> {
  final AuthRepository _authRepository;

  AuthBloc({required AuthRepository authRepository})
      : _authRepository = authRepository,
        super(const AuthState.unknown()) {
    on<AuthStatusRequested>(_onAuthStatusRequested);
    on<AuthLoginRequested>(_onAuthLoginRequested);
    on<AuthLogoutRequested>(_onAuthLogoutRequested);
  }

  void _onAuthStatusRequested(
    AuthStatusRequested event,
    Emitter<AuthState> emit,
  ) async {
    final user = await _authRepository.getCurrentUser();
    if (user != null) {
      emit(AuthState.authenticated(user));
    } else {
      emit(const AuthState.unauthenticated());
    }
  }

  void _onAuthLoginRequested(
    AuthLoginRequested event,
    Emitter<AuthState> emit,
  ) async {
    emit(state.copyWith(status: AuthStatus.loading));
    
    try {
      final user = await _authRepository.login(
        email: event.email,
        password: event.password,
      );
      emit(AuthState.authenticated(user));
    } catch (error) {
      emit(AuthState.unauthenticated(error: error.toString()));
    }
  }

  void _onAuthLogoutRequested(
    AuthLogoutRequested event,
    Emitter<AuthState> emit,
  ) async {
    await _authRepository.logout();
    emit(const AuthState.unauthenticated());
  }

  @override
  AuthState? fromJson(Map<String, dynamic> json) {
    try {
      final status = AuthStatus.values.firstWhere(
        (status) => status.toString() == json['status'] as String,
      );
      
      if (status == AuthStatus.authenticated) {
        return AuthState.authenticated(
          User.fromJson(json['user'] as Map<String, dynamic>),
        );
      }
      
      return AuthState.unauthenticated(
        error: json['error'] as String?,
      );
    } catch (_) {
      return null;
    }
  }

  @override
  Map<String, dynamic>? toJson(AuthState state) {
    return {
      'status': state.status.toString(),
      'user': state.user?.toJson(),
      'error': state.error,
    };
  }
}

// 自定义组件
// widgets/custom_button.dart
import 'package:flutter/material.dart';

enum ButtonVariant { primary, secondary, outline }
enum ButtonSize { small, medium, large }

class CustomButton extends StatelessWidget {
  final String text;
  final VoidCallback? onPressed;
  final ButtonVariant variant;
  final ButtonSize size;
  final bool isLoading;
  final Widget? icon;

  const CustomButton({
    Key? key,
    required this.text,
    this.onPressed,
    this.variant = ButtonVariant.primary,
    this.size = ButtonSize.medium,
    this.isLoading = false,
    this.icon,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    
    return SizedBox(
      height: _getButtonHeight(),
      child: ElevatedButton(
        onPressed: isLoading ? null : onPressed,
        style: _getButtonStyle(theme),
        child: isLoading
            ? SizedBox(
                width: 20,
                height: 20,
                child: CircularProgressIndicator(
                  strokeWidth: 2,
                  valueColor: AlwaysStoppedAnimation<Color>(
                    _getTextColor(theme),
                  ),
                ),
              )
            : Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  if (icon != null) ...[
                    icon!,
                    const SizedBox(width: 8),
                  ],
                  Text(
                    text,
                    style: _getTextStyle(theme),
                  ),
                ],
              ),
      ),
    );
  }

  double _getButtonHeight() {
    switch (size) {
      case ButtonSize.small:
        return 32;
      case ButtonSize.medium:
        return 44;
      case ButtonSize.large:
        return 56;
    }
  }

  ButtonStyle _getButtonStyle(ThemeData theme) {
    Color backgroundColor;
    Color borderColor = Colors.transparent;
    
    switch (variant) {
      case ButtonVariant.primary:
        backgroundColor = theme.primaryColor;
        break;
      case ButtonVariant.secondary:
        backgroundColor = theme.colorScheme.secondary;
        break;
      case ButtonVariant.outline:
        backgroundColor = Colors.transparent;
        borderColor = theme.primaryColor;
        break;
    }

    return ElevatedButton.styleFrom(
      backgroundColor: backgroundColor,
      foregroundColor: _getTextColor(theme),
      side: variant == ButtonVariant.outline 
          ? BorderSide(color: borderColor)
          : null,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(8),
      ),
      elevation: variant == ButtonVariant.outline ? 0 : 2,
    );
  }

  TextStyle _getTextStyle(ThemeData theme) {
    double fontSize;
    switch (size) {
      case ButtonSize.small:
        fontSize = 14;
        break;
      case ButtonSize.medium:
        fontSize = 16;
        break;
      case ButtonSize.large:
        fontSize = 18;
        break;
    }

    return TextStyle(
      fontSize: fontSize,
      fontWeight: FontWeight.w600,
      color: _getTextColor(theme),
    );
  }

  Color _getTextColor(ThemeData theme) {
    switch (variant) {
      case ButtonVariant.primary:
        return theme.colorScheme.onPrimary;
      case ButtonVariant.secondary:
        return theme.colorScheme.onSecondary;
      case ButtonVariant.outline:
        return theme.primaryColor;
    }
  }
}

// 性能优化的列表组件
// widgets/optimized_list_view.dart
import 'package:flutter/material.dart';

class OptimizedListView<T> extends StatelessWidget {
  final List<T> items;
  final Widget Function(BuildContext, T, int) itemBuilder;
  final Future<void> Function()? onRefresh;
  final VoidCallback? onLoadMore;
  final bool hasMore;
  final bool isLoading;

  const OptimizedListView({
    Key? key,
    required this.items,
    required this.itemBuilder,
    this.onRefresh,
    this.onLoadMore,
    this.hasMore = false,
    this.isLoading = false,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    Widget listView = ListView.builder(
      itemCount: items.length + (hasMore ? 1 : 0),
      itemBuilder: (context, index) {
        if (index >= items.length) {
          // 加载更多指示器
          if (onLoadMore != null && !isLoading) {
            // 在下一个事件循环中调用 onLoadMore
            WidgetsBinding.instance.addPostFrameCallback((_) {
              onLoadMore!();
            });
          }
          
          return const Padding(
            padding: EdgeInsets.all(16.0),
            child: Center(
              child: CircularProgressIndicator(),
            ),
          );
        }
        
        return itemBuilder(context, items[index], index);
      },
      // 性能优化设置
      cacheExtent: 1000, // 预加载范围
      physics: const AlwaysScrollableScrollPhysics(),
    );

    if (onRefresh != null) {
      return RefreshIndicator(
        onRefresh: onRefresh!,
        child: listView,
      );
    }

    return listView;
  }
}

原生集成 (Platform Channels)

dart
// services/native_service.dart
import 'package:flutter/services.dart';

class NativeService {
  static const MethodChannel _channel = MethodChannel('com.example.app/native');

  // 获取设备信息
  static Future<Map<String, dynamic>> getDeviceInfo() async {
    try {
      final result = await _channel.invokeMethod('getDeviceInfo');
      return Map<String, dynamic>.from(result);
    } on PlatformException catch (e) {
      throw Exception('获取设备信息失败: ${e.message}');
    }
  }

  // 调用原生相机
  static Future<String?> openNativeCamera() async {
    try {
      final result = await _channel.invokeMethod('openCamera');
      return result as String?;
    } on PlatformException catch (e) {
      throw Exception('打开相机失败: ${e.message}');
    }
  }

  // 设置应用图标角标 (iOS)
  static Future<void> setBadgeCount(int count) async {
    try {
      await _channel.invokeMethod('setBadgeCount', {'count': count});
    } on PlatformException catch (e) {
      print('设置角标失败: ${e.message}');
    }
  }
}

// services/secure_storage_service.dart
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class SecureStorageService {
  static const _storage = FlutterSecureStorage(
    aOptions: AndroidOptions(
      encryptedSharedPreferences: true,
    ),
    iOptions: IOSOptions(
      accessibility: IOSAccessibility.biometryCurrentSet,
    ),
  );

  static Future<void> store(String key, String value) async {
    await _storage.write(key: key, value: value);
  }

  static Future<String?> read(String key) async {
    return await _storage.read(key: key);
  }

  static Future<void> delete(String key) async {
    await _storage.delete(key: key);
  }

  static Future<void> deleteAll() async {
    await _storage.deleteAll();
  }

  // 存储认证令牌
  static Future<void> storeAuthToken(String token) async {
    await store('auth_token', token);
  }

  static Future<String?> getAuthToken() async {
    return await read('auth_token');
  }

  static Future<void> clearAuthToken() async {
    await delete('auth_token');
  }
}

3. iOS 原生开发 (Swift)

应用架构 (MVVM + Combine)

swift
// Models/User.swift
import Foundation

struct User: Codable, Identifiable {
    let id: String
    let email: String
    let name: String
    let avatar: String?
    let createdAt: Date
    
    enum CodingKeys: String, CodingKey {
        case id, email, name, avatar
        case createdAt = "created_at"
    }
}

struct LoginRequest: Codable {
    let email: String
    let password: String
}

struct LoginResponse: Codable {
    let user: User
    let token: String
}

// Services/NetworkService.swift
import Foundation
import Combine

class NetworkService {
    static let shared = NetworkService()
    private let session = URLSession.shared
    private let baseURL = "https://api.example.com"
    
    private init() {}
    
    private func request<T: Codable>(
        endpoint: String,
        method: HTTPMethod = .GET,
        body: Data? = nil,
        type: T.Type
    ) -> AnyPublisher<T, Error> {
        guard let url = URL(string: baseURL + endpoint) else {
            return Fail(error: NetworkError.invalidURL)
                .eraseToAnyPublisher()
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = method.rawValue
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        
        // 添加认证头
        if let token = UserDefaults.standard.string(forKey: "auth_token") {
            request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        }
        
        if let body = body {
            request.httpBody = body
        }
        
        return session.dataTaskPublisher(for: request)
            .map(\.data)
            .decode(type: type, decoder: JSONDecoder())
            .receive(on: DispatchQueue.main)
            .eraseToAnyPublisher()
    }
    
    func login(email: String, password: String) -> AnyPublisher<LoginResponse, Error> {
        let loginRequest = LoginRequest(email: email, password: password)
        let body = try? JSONEncoder().encode(loginRequest)
        
        return request(
            endpoint: "/auth/login",
            method: .POST,
            body: body,
            type: LoginResponse.self
        )
    }
    
    func fetchUser(id: String) -> AnyPublisher<User, Error> {
        return request(
            endpoint: "/users/\(id)",
            method: .GET,
            type: User.self
        )
    }
}

enum HTTPMethod: String {
    case GET = "GET"
    case POST = "POST"
    case PUT = "PUT"
    case DELETE = "DELETE"
}

enum NetworkError: Error {
    case invalidURL
    case noData
    case decodingError
}

// ViewModels/LoginViewModel.swift
import Foundation
import Combine

class LoginViewModel: ObservableObject {
    @Published var email = ""
    @Published var password = ""
    @Published var isLoading = false
    @Published var errorMessage: String?
    @Published var isLoggedIn = false
    
    private var cancellables = Set<AnyCancellable>()
    private let networkService = NetworkService.shared
    
    var isLoginButtonEnabled: Bool {
        !email.isEmpty && !password.isEmpty && !isLoading
    }
    
    func login() {
        guard !email.isEmpty && !password.isEmpty else {
            errorMessage = "请填写邮箱和密码"
            return
        }
        
        isLoading = true
        errorMessage = nil
        
        networkService.login(email: email, password: password)
            .sink(
                receiveCompletion: { [weak self] completion in
                    self?.isLoading = false
                    
                    if case .failure(let error) = completion {
                        self?.errorMessage = error.localizedDescription
                    }
                },
                receiveValue: { [weak self] response in
                    // 保存认证令牌
                    UserDefaults.standard.set(response.token, forKey: "auth_token")
                    
                    // 保存用户信息
                    if let userData = try? JSONEncoder().encode(response.user) {
                        UserDefaults.standard.set(userData, forKey: "user_data")
                    }
                    
                    self?.isLoggedIn = true
                }
            )
            .store(in: &cancellables)
    }
    
    func logout() {
        UserDefaults.standard.removeObject(forKey: "auth_token")
        UserDefaults.standard.removeObject(forKey: "user_data")
        isLoggedIn = false
        email = ""
        password = ""
    }
}

// Views/LoginView.swift
import SwiftUI

struct LoginView: View {
    @StateObject private var viewModel = LoginViewModel()
    
    var body: some View {
        NavigationView {
            VStack(spacing: 20) {
                // 标题
                Text("欢迎回来")
                    .font(.largeTitle)
                    .fontWeight(.bold)
                
                VStack(spacing: 16) {
                    // 邮箱输入框
                    CustomTextField(
                        text: $viewModel.email,
                        placeholder: "邮箱",
                        keyboardType: .emailAddress
                    )
                    
                    // 密码输入框
                    CustomTextField(
                        text: $viewModel.password,
                        placeholder: "密码",
                        isSecure: true
                    )
                    
                    // 错误消息
                    if let errorMessage = viewModel.errorMessage {
                        Text(errorMessage)
                            .foregroundColor(.red)
                            .font(.caption)
                    }
                    
                    // 登录按钮
                    CustomButton(
                        title: "登录",
                        isLoading: viewModel.isLoading,
                        isEnabled: viewModel.isLoginButtonEnabled
                    ) {
                        viewModel.login()
                    }
                }
                .padding(.horizontal, 24)
                
                Spacer()
            }
            .padding()
            .navigationBarHidden(true)
        }
        .fullScreenCover(isPresented: $viewModel.isLoggedIn) {
            MainTabView()
        }
    }
}

// CustomViews/CustomTextField.swift
import SwiftUI

struct CustomTextField: View {
    @Binding var text: String
    let placeholder: String
    let keyboardType: UIKeyboardType
    let isSecure: Bool
    
    init(
        text: Binding<String>,
        placeholder: String,
        keyboardType: UIKeyboardType = .default,
        isSecure: Bool = false
    ) {
        self._text = text
        self.placeholder = placeholder
        self.keyboardType = keyboardType
        self.isSecure = isSecure
    }
    
    var body: some View {
        Group {
            if isSecure {
                SecureField(placeholder, text: $text)
            } else {
                TextField(placeholder, text: $text)
                    .keyboardType(keyboardType)
                    .autocapitalization(.none)
                    .disableAutocorrection(true)
            }
        }
        .padding()
        .background(Color.gray.opacity(0.1))
        .cornerRadius(8)
        .overlay(
            RoundedRectangle(cornerRadius: 8)
                .stroke(Color.gray.opacity(0.3), lineWidth: 1)
        )
    }
}

struct CustomButton: View {
    let title: String
    let isLoading: Bool
    let isEnabled: Bool
    let action: () -> Void
    
    var body: some View {
        Button(action: action) {
            HStack {
                if isLoading {
                    ProgressView()
                        .progressViewStyle(CircularProgressViewStyle(tint: .white))
                        .scaleEffect(0.8)
                } else {
                    Text(title)
                        .fontWeight(.semibold)
                }
            }
            .frame(maxWidth: .infinity)
            .frame(height: 50)
            .background(isEnabled ? Color.blue : Color.gray)
            .foregroundColor(.white)
            .cornerRadius(10)
        }
        .disabled(!isEnabled || isLoading)
    }
}

4. Android 原生开发 (Kotlin)

MVVM 架构

kotlin
// data/models/User.kt
data class User(
    val id: String,
    val email: String,
    val name: String,
    val avatar: String? = null,
    val createdAt: String
)

data class LoginRequest(
    val email: String,
    val password: String
)

data class LoginResponse(
    val user: User,
    val token: String
)

// data/repository/AuthRepository.kt
import retrofit2.Response
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class AuthRepository @Inject constructor(
    private val apiService: ApiService,
    private val tokenManager: TokenManager
) {
    suspend fun login(email: String, password: String): Result<LoginResponse> {
        return try {
            val response = apiService.login(LoginRequest(email, password))
            if (response.isSuccessful) {
                response.body()?.let { loginResponse ->
                    tokenManager.saveToken(loginResponse.token)
                    Result.success(loginResponse)
                } ?: Result.failure(Exception("登录响应为空"))
            } else {
                Result.failure(Exception("登录失败: ${response.message()}"))
            }
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
    
    suspend fun getCurrentUser(): User? {
        return try {
            val response = apiService.getCurrentUser()
            if (response.isSuccessful) {
                response.body()
            } else {
                null
            }
        } catch (e: Exception) {
            null
        }
    }
    
    fun logout() {
        tokenManager.clearToken()
    }
    
    fun isLoggedIn(): Boolean {
        return tokenManager.getToken() != null
    }
}

// ui/login/LoginViewModel.kt
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject

data class LoginUiState(
    val isLoading: Boolean = false,
    val isLoggedIn: Boolean = false,
    val errorMessage: String? = null
)

@HiltViewModel
class LoginViewModel @Inject constructor(
    private val authRepository: AuthRepository
) : ViewModel() {
    
    private val _uiState = MutableStateFlow(LoginUiState())
    val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow()
    
    val email = MutableStateFlow("")
    val password = MutableStateFlow("")
    
    init {
        // 检查是否已登录
        _uiState.value = _uiState.value.copy(
            isLoggedIn = authRepository.isLoggedIn()
        )
    }
    
    fun login() {
        if (email.value.isEmpty() || password.value.isEmpty()) {
            _uiState.value = _uiState.value.copy(
                errorMessage = "请填写邮箱和密码"
            )
            return
        }
        
        viewModelScope.launch {
            _uiState.value = _uiState.value.copy(
                isLoading = true,
                errorMessage = null
            )
            
            authRepository.login(email.value, password.value)
                .onSuccess {
                    _uiState.value = _uiState.value.copy(
                        isLoading = false,
                        isLoggedIn = true
                    )
                }
                .onFailure { exception ->
                    _uiState.value = _uiState.value.copy(
                        isLoading = false,
                        errorMessage = exception.message
                    )
                }
        }
    }
    
    fun logout() {
        authRepository.logout()
        _uiState.value = _uiState.value.copy(isLoggedIn = false)
        email.value = ""
        password.value = ""
    }
    
    fun clearError() {
        _uiState.value = _uiState.value.copy(errorMessage = null)
    }
}

// ui/login/LoginActivity.kt
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class LoginActivity : ComponentActivity() {
    private val viewModel: LoginViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        setContent {
            AppTheme {
                LoginScreen(viewModel = viewModel)
            }
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LoginScreen(viewModel: LoginViewModel) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    val email by viewModel.email.collectAsStateWithLifecycle()
    val password by viewModel.password.collectAsStateWithLifecycle()
    
    val context = LocalContext.current
    
    // 监听登录状态变化
    LaunchedEffect(uiState.isLoggedIn) {
        if (uiState.isLoggedIn) {
            // 跳转到主页
            context.startActivity(Intent(context, MainActivity::class.java))
            (context as? Activity)?.finish()
        }
    }
    
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(24.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        // 标题
        Text(
            text = "欢迎回来",
            style = MaterialTheme.typography.headlineMedium,
            modifier = Modifier.padding(bottom = 32.dp)
        )
        
        // 邮箱输入框
        OutlinedTextField(
            value = email,
            onValueChange = viewModel.email::value::set,
            label = { Text("邮箱") },
            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email),
            modifier = Modifier.fillMaxWidth(),
            singleLine = true
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // 密码输入框
        OutlinedTextField(
            value = password,
            onValueChange = viewModel.password::value::set,
            label = { Text("密码") },
            visualTransformation = PasswordVisualTransformation(),
            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
            modifier = Modifier.fillMaxWidth(),
            singleLine = true
        )
        
        // 错误消息
        uiState.errorMessage?.let { error ->
            Text(
                text = error,
                color = MaterialTheme.colorScheme.error,
                style = MaterialTheme.typography.bodySmall,
                modifier = Modifier.padding(top = 8.dp)
            )
        }
        
        Spacer(modifier = Modifier.height(24.dp))
        
        // 登录按钮
        Button(
            onClick = viewModel::login,
            enabled = email.isNotEmpty() && password.isNotEmpty() && !uiState.isLoading,
            modifier = Modifier.fillMaxWidth()
        ) {
            if (uiState.isLoading) {
                CircularProgressIndicator(
                    modifier = Modifier.size(16.dp),
                    strokeWidth = 2.dp
                )
            } else {
                Text("登录")
            }
        }
    }
}

5. 性能优化

通用性能优化策略

typescript
// React Native 性能优化
// utils/PerformanceOptimizer.ts
import { InteractionManager, Platform } from 'react-native';

export class PerformanceOptimizer {
  // 延迟执行非关键任务
  static delayForInteraction<T>(task: () => Promise<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      InteractionManager.runAfterInteractions(() => {
        task().then(resolve).catch(reject);
      });
    });
  }

  // 内存监控
  static monitorMemoryUsage() {
    if (__DEV__) {
      setInterval(() => {
        if (Platform.OS === 'android') {
          // Android 内存监控
          console.log('Memory usage:', performance.memory);
        }
      }, 5000);
    }
  }

  // 图片优化
  static getOptimizedImageUri(uri: string, width: number, height: number): string {
    // 使用 CDN 服务进行图片压缩
    return `${uri}?w=${width}&h=${height}&q=80&f=webp`;
  }

  // 网络请求缓存
  private static cache = new Map<string, { data: any; timestamp: number }>();
  
  static async cachedFetch(url: string, options: any = {}, cacheTime = 300000): Promise<any> {
    const cacheKey = `${url}_${JSON.stringify(options)}`;
    const cached = this.cache.get(cacheKey);
    
    if (cached && Date.now() - cached.timestamp < cacheTime) {
      return cached.data;
    }
    
    const response = await fetch(url, options);
    const data = await response.json();
    
    this.cache.set(cacheKey, {
      data,
      timestamp: Date.now()
    });
    
    return data;
  }
}

// components/OptimizedImage.tsx
import React, { useState, useCallback } from 'react';
import { Image, ImageStyle, ViewStyle } from 'react-native';
import FastImage from 'react-native-fast-image';

interface OptimizedImageProps {
  source: { uri: string };
  style?: ImageStyle & ViewStyle;
  width: number;
  height: number;
  placeholder?: string;
  onLoad?: () => void;
  onError?: () => void;
}

export const OptimizedImage: React.FC<OptimizedImageProps> = ({
  source,
  style,
  width,
  height,
  placeholder,
  onLoad,
  onError,
}) => {
  const [isLoading, setIsLoading] = useState(true);
  const [hasError, setHasError] = useState(false);

  const optimizedUri = PerformanceOptimizer.getOptimizedImageUri(
    source.uri,
    width * 2, // Retina support
    height * 2
  );

  const handleLoad = useCallback(() => {
    setIsLoading(false);
    onLoad?.();
  }, [onLoad]);

  const handleError = useCallback(() => {
    setIsLoading(false);
    setHasError(true);
    onError?.();
  }, [onError]);

  if (hasError && placeholder) {
    return (
      <FastImage
        source={{ uri: placeholder }}
        style={[{ width, height }, style]}
        resizeMode={FastImage.resizeMode.cover}
      />
    );
  }

  return (
    <FastImage
      source={{ 
        uri: optimizedUri,
        priority: FastImage.priority.normal,
        cache: FastImage.cacheControl.immutable,
      }}
      style={[{ width, height }, style]}
      onLoad={handleLoad}
      onError={handleError}
      resizeMode={FastImage.resizeMode.cover}
    />
  );
};

使用技巧

1. 明确平台需求

bash
# 跨平台开发
"使用 React Native 开发同时支持 iOS 和 Android 的社交应用"

# 原生开发
"开发 iOS 应用,需要集成 ARKit 和深度相机功能"

2. 性能要求

bash
# 启动时间
"应用启动时间需要在 3 秒内"

# 内存使用
"在低端设备上运行流畅,内存使用不超过 150MB"

3. 功能需求

bash
# 原生功能
"需要集成推送通知、地理位置、相机、生物识别"

# 离线支持
"支持离线模式,数据同步功能"

最佳实践

1. 架构设计

  • 使用 MVVM 或 Redux 管理状态
  • 分层架构:UI、业务逻辑、数据层
  • 依赖注入提高可测试性

2. 性能优化

  • 延迟加载和代码分割
  • 图片压缩和缓存
  • 内存泄漏监控

3. 用户体验

  • 响应式设计适配不同屏幕
  • 流畅的动画和转场
  • 离线功能和网络容错

4. 安全考虑

  • 数据加密存储
  • 网络传输安全
  • 代码混淆和反调试

相关资源


移动开发专家 - 让应用触手可及

Claude Code 使用指南