欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
本文档将指导你从零开始创建一个完整的Flutter鸿蒙日记本应用。

📋 目录

  1. 创建项目
  2. 项目结构
  3. 核心功能实现
  4. 鸿蒙平台适配
  5. 常见问题解决

创建项目

1. 创建 Flutter 项目

flutter create --platforms ohos diary_app
cd diary_app
flutter build app --release

2. 添加依赖

编辑 pubspec.yaml

name: diary_app
description: 鸿蒙日记本应用

publish_to: 'none'

version: 1.0.0+1

environment:
  sdk: '>=3.0.0 <4.0.0'

dependencies:
  flutter:
    sdk: flutter
  
  # 国际化
  intl: ^0.18.0
  
  # 文件存储(纯 Dart 实现,兼容鸿蒙)
  path: ^1.8.3
  
  # UI 动画
  flutter_staggered_animations: ^1.1.1

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0

flutter:
  uses-material-design: true

安装依赖:

flutter pub get

项目结构

lib/
├── main.dart                          # 应用入口
├── models/
│   └── diary_model.dart               # 日记数据模型
├── screens/
│   ├── home_screen.dart               # 主页(日记列表)
│   ├── diary_editor_screen.dart       # 日记编辑页面
│   ├── diary_detail_screen.dart       # 日记详情页面
│   └── stats_screen.dart              # 统计信息页面
└── services/
    ├── shared_preferences_storage_service.dart  # 文件存储服务
    ├── database_helper.dart                     # 数据库操作帮助类
    └── image_storage_service.dart               # 图片存储服务

核心功能实现

1. 数据模型 (diary_model.dart)

import 'package:flutter/material.dart';

/// 日记数据模型
class Diary {
  final int? id;
  final String title;
  final String content;
  final String mood;
  final String weather;
  final String? imagePath;
  final DateTime createdAt;
  final DateTime updatedAt;

  Diary({
    this.id,
    required this.title,
    required this.content,
    required this.mood,
    required this.weather,
    this.imagePath,
    required this.createdAt,
    required this.updatedAt,
  });

  /// 从 Map 对象转换为 Diary 对象
  factory Diary.fromMap(Map<String, dynamic> map) {
    return Diary(
      id: map['id'] as int?,
      title: map['title'] as String,
      content: map['content'] as String,
      mood: map['mood'] as String,
      weather: map['weather'] as String,
      imagePath: map['imagePath'] as String?,
      createdAt: DateTime.parse(map['createdAt'] as String),
      updatedAt: DateTime.parse(map['updatedAt'] as String),
    );
  }

  /// 转换为 Map 对象
  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'title': title,
      'content': content,
      'mood': mood,
      'weather': weather,
      'imagePath': imagePath,
      'createdAt': createdAt.toIso8601String(),
      'updatedAt': updatedAt.toIso8601String(),
    };
  }

  Diary copyWith({
    int? id,
    String? title,
    String? content,
    String? mood,
    String? weather,
    String? imagePath,
    DateTime? createdAt,
    DateTime? updatedAt,
  }) {
    return Diary(
      id: id ?? this.id,
      title: title ?? this.title,
      content: content ?? this.content,
      mood: mood ?? this.mood,
      weather: weather ?? this.weather,
      imagePath: imagePath ?? this.imagePath,
      createdAt: createdAt ?? this.createdAt,
      updatedAt: updatedAt ?? this.updatedAt,
    );
  }
}

/// 心情选项
class MoodOption {
  final String value;
  final String display;
  final IconData icon;

  const MoodOption({
    required this.value,
    required this.display,
    required this.icon,
  });

  static const List<MoodOption> options = [
    MoodOption(value: 'happy', display: '开心', icon: Icons.sentiment_very_satisfied),
    MoodOption(value: 'calm', display: '平静', icon: Icons.sentiment_satisfied),
    MoodOption(value: 'sad', display: '悲伤', icon: Icons.sentiment_dissatisfied),
    MoodOption(value: 'excited', display: '兴奋', icon: Icons.sentiment_very_satisfied),
  ];

  static MoodOption fromValue(String value) {
    return options.firstWhere(
      (option) => option.value == value,
      orElse: () => options.first,
    );
  }
}

/// 天气选项
class WeatherOption {
  final String value;
  final String display;
  final IconData icon;

  const WeatherOption({
    required this.value,
    required this.display,
    required this.icon,
  });

  static const List<WeatherOption> options = [
    WeatherOption(value: 'sunny', display: '晴', icon: Icons.wb_sunny),
    WeatherOption(value: 'cloudy', display: '多云', icon: Icons.cloud),
    WeatherOption(value: 'rainy', display: '雨', icon: Icons.umbrella),
    WeatherOption(value: 'snowy', display: '雪', icon: Icons.ac_unit),
  ];

  static WeatherOption fromValue(String value) {
    return options.firstWhere(
      (option) => option.value == value,
      orElse: () => options.first,
    );
  }
}

2. 文件存储服务 (shared_preferences_storage_service.dart)

import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as p;

/// 基于文件的日记存储服务
/// 纯 Dart 实现,完美兼容鸿蒙平台
class FileStorageService {
  static final FileStorageService _instance = FileStorageService._internal();
  factory FileStorageService() => _instance;
  FileStorageService._internal();

  static const String _storageFileName = 'diaries.json';
  final Map<String, Map<String, dynamic>> _diaryCache = {};
  File? _storageFile;

  /// 初始化存储服务
  Future<void> init() async {
    try {
      final appDir = await _getApplicationDataDirectory();
      _storageFile = File(p.join(appDir.path, _storageFileName));

      if (!await appDir.exists()) {
        await appDir.create(recursive: true);
      }

      await _loadFromFile();
    } catch (e) {
      rethrow;
    }
  }

  /// 获取应用数据目录
  Future<Directory> _getApplicationDataDirectory() async {
    String dataPath = Directory.systemTemp.path;
    dataPath = p.join(dataPath, 'diary_app');
    return Directory(dataPath);
  }

  /// 从文件加载数据
  Future<void> _loadFromFile() async {
    if (_storageFile != null && await _storageFile!.exists()) {
      try {
        final jsonString = await _storageFile!.readAsString();
        if (jsonString.isNotEmpty) {
          final Map<String, dynamic> data = jsonDecode(jsonString) as Map<String, dynamic>;
          _diaryCache.clear();
          data.forEach((key, value) {
            _diaryCache[key] = Map<String, dynamic>.from(value as Map);
          });
        }
      } catch (e) {
        await _saveToFile();
      }
    } else {
      await _saveToFile();
    }
  }

  /// 保存数据到文件
  Future<void> _saveToFile() async {
    if (_storageFile != null) {
      final jsonString = jsonEncode(_diaryCache);
      await _storageFile!.writeAsString(jsonString);
    }
  }

  /// 保存日记
  Future<void> saveDiary(String key, Map<String, dynamic> data) async {
    _diaryCache[key] = data;
    await _saveToFile();
  }

  /// 获取日记
  Future<Map<String, dynamic>?> getDiary(String key) async {
    return _diaryCache[key];
  }

  /// 获取所有日记
  Future<List<Map<String, dynamic>>> getAllDiaries() async {
    await _loadFromFile(); // 关键:每次获取前都从文件重新加载
    final allData = _diaryCache.values.toList();
    allData.sort((a, b) {
      final aTime = DateTime.parse(a['createdAt'] as String);
      final bTime = DateTime.parse(b['createdAt'] as String);
      return bTime.compareTo(aTime);
    });
    return allData;
  }

  /// 删除日记
  Future<void> deleteDiary(String key) async {
    _diaryCache.remove(key);
    await _saveToFile();
  }

  /// 获取日记总数
  Future<int> getDiaryCount() async {
    return _diaryCache.length;
  }

  /// 获取连续写作天数
  Future<int> getContinuousWritingDays() async {
    final allDiaries = await getAllDiaries();
    if (allDiaries.isEmpty) return 0;

    final dates = allDiaries
        .map((d) => DateTime.parse(d['createdAt'] as String))
        .map((dt) => DateTime(dt.year, dt.month, dt.day))
        .toSet()
        .toList();

    dates.sort((a, b) => b.compareTo(a));

    if (dates.isEmpty) return 0;

    int continuousDays = 1;
    final today = DateTime.now();
    final yesterday = DateTime(today.year, today.month, today.day - 1);
    final latestDate = dates.first;
    
    if (latestDate.isBefore(yesterday) && latestDate.isBefore(today)) {
      return 0;
    }

    for (int i = 0; i < dates.length - 1; i++) {
      final diff = dates[i].difference(dates[i + 1]).inDays;
      if (diff == 1) {
        continuousDays++;
      } else {
        break;
      }
    }

    return continuousDays;
  }

  /// 搜索日记
  Future<List<Map<String, dynamic>>> searchDiaries(String keyword) async {
    if (keyword.trim().isEmpty) {
      return getAllDiaries();
    }

    final allDiaries = await getAllDiaries();
    final keywordLower = keyword.toLowerCase();

    return allDiaries.where((diary) {
      final title = (diary['title'] as String).toLowerCase();
      final content = (diary['content'] as String).toLowerCase();
      return title.contains(keywordLower) || content.contains(keywordLower);
    }).toList();
  }
}

3. 数据库帮助类 (database_helper.dart)

import '../models/diary_model.dart';
import 'shared_preferences_storage_service.dart';

/// 数据库操作帮助类
class DatabaseHelper {
  static final DatabaseHelper _instance = DatabaseHelper._internal();
  factory DatabaseHelper() => _instance;
  DatabaseHelper._internal();

  late final FileStorageService _storage = FileStorageService();

  /// 初始化数据库
  Future<void> init() async {
    await _storage.init();
  }

  /// 保存日记
  Future<int> saveDiary(Diary diary) async {
    final now = DateTime.now();
    final diaryMap = {
      'id': diary.id ?? now.millisecondsSinceEpoch ~/ 1000,
      'title': diary.title,
      'content': diary.content,
      'mood': diary.mood,
      'weather': diary.weather,
      'imagePath': diary.imagePath,
      'createdAt': diary.createdAt.toIso8601String(),
      'updatedAt': diary.updatedAt.toIso8601String(),
    };

    final key = diaryMap['id'].toString();
    await _storage.saveDiary(key, diaryMap);
    return diaryMap['id'] as int;
  }

  /// 获取所有日记
  Future<List<Diary>> getAllDiaries() async {
    final diaries = await _storage.getAllDiaries();
    return diaries.map((map) => Diary.fromMap(map)).toList();
  }

  /// 获取日记详情
  Future<Diary?> getDiaryById(int id) async {
    final diaryMap = await _storage.getDiary(id.toString());
    if (diaryMap == null) return null;
    return Diary.fromMap(diaryMap);
  }

  /// 删除日记
  Future<void> deleteDiary(int id) async {
    await _storage.deleteDiary(id.toString());
  }

  /// 批量删除日记
  Future<void> deleteDiaries(List<int> ids) async {
    for (final id in ids) {
      await _storage.deleteDiary(id.toString());
    }
  }

  /// 更新日记
  Future<void> updateDiary(Diary diary) async {
    await saveDiary(diary);
  }

  /// 插入日记
  Future<int> insertDiary(Diary diary) async {
    return await saveDiary(diary);
  }

  /// 获取日记总数
  Future<int> getDiaryCount() async {
    return await _storage.getDiaryCount();
  }

  /// 获取连续写作天数
  Future<int> getContinuousWritingDays() async {
    return await _storage.getContinuousWritingDays();
  }

  /// 搜索日记
  Future<List<Diary>> searchDiaries(String keyword) async {
    final diaries = await _storage.searchDiaries(keyword);
    return diaries.map((map) => Diary.fromMap(map)).toList();
  }
}

4. 应用入口 (main.dart)

import 'package:flutter/material.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'screens/home_screen.dart';
import 'services/database_helper.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // 初始化日期本地化
  await initializeDateFormatting('zh_CN', null);

  // 初始化数据库
  final dbHelper = DatabaseHelper();
  await dbHelper.init();

  runApp(const DiaryApp());
}

class DiaryApp extends StatelessWidget {
  const DiaryApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '我的日记',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const HomeScreen(),
    );
  }
}

5. 主页 (home_screen.dart)

import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import '../services/database_helper.dart';
import '../models/diary_model.dart';
import 'diary_editor_screen.dart';
import 'diary_detail_screen.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final DatabaseHelper _dbHelper = DatabaseHelper();
  List<Diary> _diaries = [];
  bool _isLoading = true;

  
  void initState() {
    super.initState();
    _loadData();
  }

  /// 加载所有数据
  Future<void> _loadData() async {
    try {
      setState(() => _isLoading = true);

      final diaries = await _dbHelper.getAllDiaries();
      final totalCount = await _dbHelper.getDiaryCount();
      final continuousDays = await _dbHelper.getContinuousWritingDays();

      setState(() {
        _diaries = diaries;
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _diaries = [];
        _isLoading = false;
      });
    }
  }

  /// 打开编辑器
  Future<void> _openEditor([Diary? diary]) async {
    final result = await Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => DiaryEditorScreen(diary: diary),
      ),
    );

    if (result == true) {
      await _loadData();
    }
  }

  /// 打开详情
  Future<void> _openDetail(Diary diary) async {
    final result = await Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => DiaryDetailScreen(diary: diary),
      ),
    );

    if (result == true) {
      await _loadData();
    }
  }

  /// 显示统计信息
  void _showStatsDialog() async {
    final totalCount = await _dbHelper.getDiaryCount();
    final continuousDays = await _dbHelper.getContinuousWritingDays();

    if (!mounted) return;

    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('统计信息'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            ListTile(
              leading: const Icon(Icons.description),
              title: const Text('日记总数'),
              trailing: Text('$totalCount 篇'),
            ),
            ListTile(
              leading: const Icon(Icons.calendar_today),
              title: const Text('连续写作'),
              trailing: Text('$continuousDays 天'),
            ),
          ],
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('关闭'),
          ),
        ],
      ),
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('我的日记'),
        actions: [
          IconButton(
            icon: const Icon(Icons.bar_chart),
            onPressed: _showStatsDialog,
          ),
          IconButton(
            icon: const Icon(Icons.search),
            onPressed: () {},
          ),
        ],
      ),
      body: _isLoading
          ? const Center(child: CircularProgressIndicator())
          : _diaries.isEmpty
              ? _buildEmptyState()
              : _buildDiaryList(),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _openEditor(),
        child: const Icon(Icons.add),
      ),
    );
  }

  Widget _buildEmptyState() {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(
            Icons.auto_awesome,
            size: 80,
            color: Theme.of(context).colorScheme.primary.withOpacity(0.5),
          ),
          const SizedBox(height: 16),
          Text(
            '还没有任何日记',
            style: TextStyle(
              fontSize: 18,
              color: Colors.grey.shade600,
            ),
          ),
          const SizedBox(height: 8),
          Text(
            '点击右下角的按钮开始记录',
            style: TextStyle(
              fontSize: 14,
              color: Colors.grey.shade500,
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildDiaryList() {
    return AnimationLimiter(
      child: ListView.builder(
        padding: const EdgeInsets.all(16),
        itemCount: _diaries.length,
        itemBuilder: (context, index) {
          final diary = _diaries[index];
          return AnimationConfiguration.staggeredList(
            position: index,
            duration: const Duration(milliseconds: 375),
            child: SlideAnimation(
              verticalOffset: 50.0,
              child: FadeInAnimation(
                child: Card(
                  margin: const EdgeInsets.only(bottom: 12),
                  child: ListTile(
                    leading: Icon(
                      MoodOption.fromValue(diary.mood).icon,
                      color: Theme.of(context).colorScheme.primary,
                    ),
                    title: Text(diary.title),
                    subtitle: Text(
                      diary.content,
                      maxLines: 2,
                      overflow: TextOverflow.ellipsis,
                    ),
                    trailing: Text(
                      '${diary.createdAt.month}/${diary.createdAt.day}',
                      style: TextStyle(color: Colors.grey.shade600),
                    ),
                    onTap: () => _openDetail(diary),
                  ),
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}

6. 日记编辑器 (diary_editor_screen.dart)

import 'package:flutter/material.dart';
import '../models/diary_model.dart';
import '../services/database_helper.dart';

class DiaryEditorScreen extends StatefulWidget {
  final Diary? diary;

  const DiaryEditorScreen({super.key, this.diary});

  
  State<DiaryEditorScreen> createState() => _DiaryEditorScreenState();
}

class _DiaryEditorScreenState extends State<DiaryEditorScreen> {
  final DatabaseHelper _dbHelper = DatabaseHelper();
  final TextEditingController _titleController = TextEditingController();
  final TextEditingController _contentController = TextEditingController();
  String _selectedMood = 'happy';
  String _selectedWeather = 'sunny';
  bool _isSaving = false;
  bool _isEditing = false;

  
  void initState() {
    super.initState();
    _isEditing = widget.diary != null;
    if (_isEditing) {
      _titleController.text = widget.diary!.title;
      _contentController.text = widget.diary!.content;
      _selectedMood = widget.diary!.mood;
      _selectedWeather = widget.diary!.weather;
    }
  }

  
  void dispose() {
    _titleController.dispose();
    _contentController.dispose();
    super.dispose();
  }

  Future<void> _saveDiary() async {
    if (_titleController.text.trim().isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('请输入标题')),
      );
      return;
    }

    setState(() => _isSaving = true);

    try {
      final now = DateTime.now();

      if (_isEditing) {
        final updatedDiary = widget.diary!.copyWith(
          title: _titleController.text.trim(),
          content: _contentController.text.trim(),
          mood: _selectedMood,
          weather: _selectedWeather,
          updatedAt: now,
        );
        await _dbHelper.updateDiary(updatedDiary);
      } else {
        final newDiary = Diary(
          title: _titleController.text.trim(),
          content: _contentController.text.trim(),
          mood: _selectedMood,
          weather: _selectedWeather,
          createdAt: now,
          updatedAt: now,
        );
        await _dbHelper.insertDiary(newDiary);
      }

      if (mounted) {
        Navigator.pop(context, true);
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('保存失败:$e')),
      );
      setState(() => _isSaving = false);
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(_isEditing ? '编辑日记' : '写日记'),
        actions: [
          IconButton(
            icon: const Icon(Icons.check),
            onPressed: _isSaving ? null : _saveDiary,
          ),
        ],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            TextField(
              controller: _titleController,
              decoration: const InputDecoration(
                labelText: '标题',
                border: OutlineInputBorder(),
              ),
              style: const TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                const Text('心情:'),
                ...MoodOption.options.map((option) => Padding(
                  padding: const EdgeInsets.only(right: 8),
                  child: ChoiceChip(
                    label: Text(option.display),
                    selected: _selectedMood == option.value,
                    onSelected: (selected) {
                      if (selected) {
                        setState(() => _selectedMood = option.value);
                      }
                    },
                  ),
                )),
              ],
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                const Text('天气:'),
                ...WeatherOption.options.map((option) => Padding(
                  padding: const EdgeInsets.only(right: 8),
                  child: ChoiceChip(
                    label: Text(option.display),
                    selected: _selectedWeather == option.value,
                    onSelected: (selected) {
                      if (selected) {
                        setState(() => _selectedWeather = option.value);
                      }
                    },
                  ),
                )),
              ],
            ),
            const SizedBox(height: 16),
            TextField(
              controller: _contentController,
              decoration: const InputDecoration(
                labelText: '内容',
                border: OutlineInputBorder(),
              ),
              maxLines: 10,
            ),
          ],
        ),
      ),
    );
  }
}

7. 日记详情 (diary_detail_screen.dart)

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../models/diary_model.dart';
import 'diary_editor_screen.dart';

class DiaryDetailScreen extends StatelessWidget {
  final Diary diary;

  const DiaryDetailScreen({super.key, required this.diary});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(diary.title),
        actions: [
          IconButton(
            icon: const Icon(Icons.edit),
            onPressed: () async {
              final result = await Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => DiaryEditorScreen(diary: diary),
                ),
              );

              if (result == true && context.mounted) {
                Navigator.pop(context, true);
              }
            },
          ),
        ],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(
                  MoodOption.fromValue(diary.mood).icon,
                  size: 32,
                  color: Theme.of(context).colorScheme.primary,
                ),
                const SizedBox(width: 8),
                Icon(
                  WeatherOption.fromValue(diary.weather).icon,
                  size: 24,
                  color: Colors.grey.shade600,
                ),
                const Spacer(),
                Text(
                  DateFormat('yyyy-MM-dd HH:mm').format(diary.createdAt),
                  style: TextStyle(color: Colors.grey.shade600),
                ),
              ],
            ),
            const Divider(height: 32),
            Text(
              diary.content,
              style: const TextStyle(fontSize: 16, height: 1.6),
            ),
          ],
        ),
      ),
    );
  }
}

鸿蒙平台适配

关键要点

  1. 使用纯 Dart 实现存储

    • 避免使用 path_providershared_preferences 等依赖平台通道的插件
    • 使用 dart:ioFileDirectory 进行文件操作
  2. 单例模式确保数据一致性

    • FileStorageService 使用单例模式
    • 每次读取数据前从文件重新加载
  3. 字段命名一致性

    • 保存和读取使用相同的字段命名

常见问题

问题 1:MissingPluginException

错误信息

MissingPluginException(No implementation found for method getApplicationDocumentsDirectory on channel plugins.flutter.io/path_provider)

解决方案
使用纯 Dart 的 Directory.systemTemp 代替:

String dataPath = Directory.systemTemp.path;
dataPath = p.join(dataPath, 'diary_app');
问题 2:数据保存后主页不更新

原因:缓存没有从文件重新加载

解决方案
getAllDiaries() 中每次获取前都从文件重新加载:

Future<List<Map<String, dynamic>>> getAllDiaries() async {
  await _loadFromFile(); // 关键修复
  final allData = _diaryCache.values.toList();
  // ...
}
问题 3:权限错误

错误信息

PathAccessException: Creation failed, path = 'xxx' (OS Error: Permission denied, errno = 13)

解决方案
使用系统临时目录,不需要额外权限:

String dataPath = Directory.systemTemp.path;

运行项目

1. 连接设备或启动模拟器

在 DevEco Studio 中:

  • 连接真机(开发者模式 + USB 调试)
  • 或启动 OpenHarmony 模拟器

2. 运行应用

# 方式 1:使用 Flutter 命令
flutter run -d <device-id>

# 方式 2:在 DevEco Studio 中点击运行按钮

3. 查看日志

# 查看 Flutter 日志
flutter logs

# 或在 DevEco Studio 的 Log 窗口查看

项目优化建议

1. 性能优化

  • 使用 ListView.builder 懒加载长列表
  • 图片压缩和缓存
  • 数据库分页加载

2. 功能增强

  • 日记分类/标签
  • 日记导出(PDF、图片)
  • 云同步备份
  • 密码保护

3. UI/UX 改进

  • 主题切换(深色模式)
  • 日记封面图片
  • 心情/天气统计图表
  • 日历视图

成果展示

首页
写日记

在这里插入图片描述


总结

本项目展示了如何在鸿蒙平台上开发纯 Dart 实现的 Flutter 应用,关键点是:

  1. 避免平台依赖 - 使用纯 Dart IO 进行文件存储
  2. 数据一致性 - 单例模式 + 文件重新加载
  3. 字段一致性 - 保存和读取使用相同字段名
  4. 错误处理 - 完善的异常捕获和日志输出

通过以上步骤,你可以成功复现一个完整的鸿蒙日记本应用!🎉


Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐