Как разработать мобильное приложение с нуля в 2026 году
Вы определились с идеей мобильного приложения. Что дальше? Эта статья - пошаговый процесс от идеи до публикации, без воды и маркетинговых клише.
Этап 1: Определение требований (1-2 недели)
Прежде чем написать строчку кода, нужно ответить на несколько вопросов:
Кто ваши пользователи? Не "все люди", а конкретный сегмент. Курьеры доставки? Пациенты клиники? Менеджеры склада? От ответа зависит UX, платформа и функционал.
iOS или Android? Или оба?
| Сценарий | Рекомендация |
|---|---|
| Аудитория в СНГ, бюджет ограничен | Android в приоритете (70-80% рынка) |
| Корпоративное приложение | Зависит от устройств компании |
| Премиум-продукт / высокий чек | iOS в приоритете |
| Стартап с инвестициями | Кроссплатформа сразу (Flutter / React Native) |
Что входит в MVP? MVP (минимально жизнеспособный продукт) - это не "приложение со всеми фичами, но попроще". Это минимальный набор функций, который решает core-проблему пользователя. Всё остальное - в следующих версиях.
Пример: приложение для записи к врачу.
- MVP: список врачей → выбор слота → запись → уведомление. Всё.
- Не MVP: история посещений, телемедицина, лаборатория, рейтинги, чат, аналитика - это версия 2.0+.
Этап 2: Проектирование (2-3 недели)
Архитектура
Типичная архитектура мобильного приложения:
Мобильное приложение (iOS / Android)
↓ HTTPS / REST API или GraphQL
Backend API (Node.js / Python / Go)
↓
База данных (PostgreSQL / MongoDB)
↓
Внешние сервисы (оплата, SMS, push-уведомления)
Wireframes и прототип
До разработки нужны экраны. Используйте Figma - это стандарт индустрии:
- Low-fidelity (серые блоки): структура и навигация, без дизайна
- High-fidelity (полный дизайн): цвета, шрифты, компоненты
- Интерактивный прототип: кликабельный, для тестирования с пользователями
Стоимость дизайна: от $500 (простое приложение, 10-15 экранов) до $3000+ (сложное, 40+ экранов).
Этап 3: Выбор технологии
Нативная разработка
// iOS - Swift
import SwiftUI
struct AppointmentCard: View {
let appointment: Appointment
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text(appointment.doctorName)
.font(.headline)
Text(appointment.formattedDate)
.foregroundStyle(.secondary)
}
.padding()
.background(.white)
.cornerRadius(12)
}
}
// Android - Kotlin + Jetpack Compose
@Composable
fun AppointmentCard(appointment: Appointment) {
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(12.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = appointment.doctorName,
style = MaterialTheme.typography.headlineSmall
)
Text(
text = appointment.formattedDate,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
Когда выбирать нативное: сложные анимации, нужен полный доступ к API устройства (ARKit, Core Motion, глубокие нотификации), максимальная производительность.
Кроссплатформа: Flutter (рекомендую для большинства проектов)
// Flutter - один код для iOS, Android и web
class AppointmentCard extends StatelessWidget {
final Appointment appointment;
const AppointmentCard({required this.appointment});
@override
Widget build(BuildContext context) {
return Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
appointment.doctorName,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 4),
Text(
appointment.formattedDate,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
],
),
),
);
}
}
Преимущества Flutter: один кодбейс для iOS + Android, быстрое время разработки, отличная производительность, большой выбор пакетов. Хорошо для стартапов и продуктов с ограниченным бюджетом.
Кроссплатформа: React Native (если команда знает React)
// React Native - знакомо всем React-разработчикам
function AppointmentCard({ appointment }: { appointment: Appointment }) {
return (
<View style={styles.card}>
<Text style={styles.doctorName}>{appointment.doctorName}</Text>
<Text style={styles.date}>{appointment.formattedDate}</Text>
</View>
);
}
const styles = StyleSheet.create({
card: {
backgroundColor: '#fff',
borderRadius: 12,
padding: 16,
marginVertical: 6,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.08,
shadowRadius: 4,
elevation: 2,
},
doctorName: { fontSize: 16, fontWeight: '600' },
date: { fontSize: 14, color: '#666', marginTop: 4 },
});
Этап 4: Разработка (6-16 недель в зависимости от сложности)
Структура проекта (Flutter)
lib/
├── core/
│ ├── api/ # HTTP-клиент, endpoints
│ ├── auth/ # логика аутентификации
│ ├── errors/ # классы ошибок
│ └── theme/ # цвета, шрифты
├── features/
│ ├── appointments/
│ │ ├── data/ # API-вызовы, модели
│ │ ├── domain/ # бизнес-логика, entities
│ │ └── ui/ # экраны, виджеты
│ ├── auth/
│ └── profile/
└── main.dart
Управление состоянием (Flutter + Riverpod)
// features/appointments/data/appointments_repository.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'appointments_repository.g.dart';
@riverpod
Future<List<Appointment>> userAppointments(
UserAppointmentsRef ref,
String userId,
) async {
final api = ref.watch(apiClientProvider);
return api.getAppointments(userId: userId);
}
// features/appointments/ui/appointments_screen.dart
class AppointmentsScreen extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(currentUserProvider);
final appointmentsAsync = ref.watch(userAppointmentsProvider(user!.id));
return Scaffold(
appBar: AppBar(title: const Text('Мои записи')),
body: appointmentsAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (err, stack) => ErrorWidget(err.toString()),
data: (appointments) => appointments.isEmpty
? const EmptyState(message: 'У вас пока нет записей')
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: appointments.length,
itemBuilder: (_, i) => AppointmentCard(appointment: appointments[i]),
),
),
);
}
}
Аутентификация
// features/auth/data/auth_service.dart
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:dio/dio.dart';
class AuthService {
final Dio _dio;
final FlutterSecureStorage _storage;
AuthService(this._dio, this._storage);
Future<User> login(String phone, String password) async {
final response = await _dio.post('/auth/login', data: {
'phone': phone,
'password': password,
});
final token = response.data['accessToken'] as String;
// Сохраняем токен в защищённое хранилище (Keychain/Keystore)
await _storage.write(key: 'access_token', value: token);
return User.fromJson(response.data['user'] as Map<String, dynamic>);
}
Future<void> logout() async {
await _storage.delete(key: 'access_token');
}
Future<String?> getToken() async {
return _storage.read(key: 'access_token');
}
}
Push-уведомления (Firebase)
// В pubspec.yaml:
# firebase_core: ^3.0.0
# firebase_messaging: ^15.0.0
// main.dart
import 'package:firebase_messaging/firebase_messaging.dart';
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// Обработка уведомлений когда приложение закрыто
print('Background message: ${message.notification?.title}');
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
runApp(const MyApp());
}
// Запрос разрешения и получение токена
Future<void> setupPushNotifications(String userId) async {
final messaging = FirebaseMessaging.instance;
final settings = await messaging.requestPermission(
alert: true,
badge: true,
sound: true,
);
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
final token = await messaging.getToken();
if (token != null) {
await ApiClient.instance.savePushToken(userId: userId, token: token);
}
// Обновляем токен при ротации
messaging.onTokenRefresh.listen((newToken) {
ApiClient.instance.savePushToken(userId: userId, token: newToken);
});
}
}
Этап 5: Тестирование (2-3 недели)
Unit-тесты
// test/appointments_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
void main() {
group('AppointmentRepository', () {
late MockApiClient mockApi;
late AppointmentRepository repo;
setUp(() {
mockApi = MockApiClient();
repo = AppointmentRepository(mockApi);
});
test('возвращает список записей', () async {
final mockAppointments = [
Appointment(id: '1', doctorName: 'Иванов И.И.', date: DateTime.now()),
];
when(mockApi.getAppointments(userId: '123'))
.thenAnswer((_) async => mockAppointments);
final result = await repo.getAppointments('123');
expect(result, equals(mockAppointments));
});
test('выбрасывает исключение при ошибке сети', () async {
when(mockApi.getAppointments(userId: any))
.thenThrow(NetworkException('Нет соединения'));
expect(
() => repo.getAppointments('123'),
throwsA(isA<NetworkException>()),
);
});
});
}
Тестирование на устройствах
Перед релизом тестируйте на:
- Минимум 2 Android-устройствах (бюджетное + флагман)
- Минимум 2 iOS-устройствах (iPhone SE и iPhone Pro для разных размеров экрана)
- Медленный интернет (3G) - самый важный тест для СНГ-рынка
- Разные версии ОС: Android 10+ и iOS 15+
Этап 6: Публикация (1-2 недели)
Google Play
# Сборка релизного APK / AAB (рекомендую AAB)
flutter build appbundle --release
# Или с кастомным ключом подписи
flutter build appbundle --release \
--build-name=1.0.0 \
--build-number=1
Что нужно для Google Play:
- Аккаунт разработчика Google Play (разовая оплата $25)
- Скриншоты: минимум 2, максимум 8 (телефон + планшет опционально)
- Иконка приложения 512x512px
- Политика конфиденциальности (обязательно, даже если нет личных данных)
- Описание на языке целевой аудитории
Срок проверки: обычно 1-3 дня (первый релиз может занять 7 дней).
App Store
# Для iOS нужен Mac с Xcode
flutter build ipa --release
# Загрузка через Xcode или Transporter
Что нужно для App Store:
- Аккаунт Apple Developer ($99/год)
- Минимум 3 скриншота на каждый размер экрана
- Иконка без прозрачности (альфа-канал запрещён)
- Подробное описание функционала в форме для Apple
Срок проверки: 1-2 дня (но бывает дольше, особенно при первом релизе).
Типичные ошибки при разработке мобильного приложения
Слишком большой MVP. Первая версия должна решать одну задачу хорошо. Если вы перечисляете 10 функций в MVP - это не MVP.
Игнорирование офлайн-режима. Пользователи в СНГ часто имеют нестабильный интернет. Приложение должно работать при потере соединения и синхронизироваться при восстановлении.
Нет аналитики с первого дня. Установите Firebase Analytics или Amplitude до релиза. Без данных вы не знаете, что пользователи делают и где уходят.
Хранение чувствительных данных в SharedPreferences. Токены, пароли, данные карт - только в Keychain (iOS) / Keystore (Android) или через flutter_secure_storage.
Запрашивать разрешения сразу. Не запрашивайте разрешение на камеру при открытии приложения. Спрашивайте в момент, когда пользователь совершает действие, требующее этого разрешения.
Сколько стоит разработать мобильное приложение
| Тип приложения | Срок | Стоимость |
|---|---|---|
| Простое (каталог, форма записи) | 4-6 недель | от $3 000 |
| Средней сложности (маркетплейс, доставка) | 8-14 недель | от $8 000 |
| Сложное (социальная сеть, финтех) | 4-8 месяцев | от $20 000 |
| Enterprise (корпоративная система) | 6-12 месяцев | от $40 000 |
Aunimeda разрабатывает мобильные приложения для iOS и Android на Flutter и React Native - от проектирования до публикации в сторах.
Обсудить проект. Смотрите также: Мобильные приложения, Приложение доставки, Разработка ПО