Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ jobs:

# Spec 0006 criterion 14a: an example that depends on `any` builds
# against whatever is newest that day, so no example pubspec names
# `any`, or no constraint, under dependencies or dev_dependencies.
# `any`, or no constraint, under dependencies or dev_dependencies, and
# a git dependency pins a full commit hash.
- name: Example constraints
if: success() || failure()
working-directory: tool
Expand Down
3 changes: 3 additions & 0 deletions AUTHORING.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,9 @@ watch deviations, each listed in the package `README.md` and
- `Platform.isIOS` becomes `FlutterWatchosPlatform.isIos` where the call it
guards reaches a plugin with no watchOS implementation.
- A missing `*_watchos` package is added to the example's pubspec.
- A dependency that upstream's example takes from upstream's repository,
because it is not on pub.dev, is pinned with `ref` to the commit of the
upstream release the package is tested against.
- An upstream comment with a forbidden word is reworded.

If upstream's example cannot launch on the watch even with these deviations
Expand Down
7 changes: 4 additions & 3 deletions packages/sqflite_watchos/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ physical watch.
minor.
* Two FFI exports: `sqflite_watchos_databases_path` and
`sqflite_watchos_sqlite_version`.
* The example's `lib/` is a small watch demo, because sqflite's own example
depends on the unpublished `sqflite_example_common`; its official
integration test is upstream's, unchanged.
* The example is sqflite 2.4.5's own. It depends on `sqflite_example_common`,
which is not on pub.dev, so its pubspec pins that package to the
tekartik/sqflite commit of the release. Its official integration test is
upstream's, unchanged.
27 changes: 23 additions & 4 deletions packages/sqflite_watchos/PORTING_REPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,29 @@ replaced by `Platform.operatingSystemVersion`.

## Example

sqflite's own example app is built on `sqflite_example_common`, which is
not published, so `example/lib/main.dart` is a small watch demo instead.
`example/integration_test/sqflite_test.dart` is upstream's test, unchanged but for
the formatting this repo's CI checks.
`example/` is sqflite 2.4.5's own example (`sqflite/example` in
tekartik/sqflite): `lib/main.dart` and `lib/compat_test_run.dart` are
upstream's, unchanged. Its pages come from `sqflite_example_common`, which is
not published on pub.dev. The watch deviations are both in
`example/pubspec.yaml`:

| Change | Why |
|---|---|
| `sqflite_example_common` is pinned with `ref` to tekartik/sqflite `21d2b8d` | it is not on pub.dev, and upstream's pubspec takes it from the repository's default branch, which can move under the example; `21d2b8d` is the commit of sqflite 2.4.5 |
| `sqflite_watchos` is added | the watch implementation |

The other dependencies are upstream's, each with a caret on the version it
ran with here, as this repository's CI asks.
`example/integration_test/sqflite_test.dart` is upstream's test, unchanged
but for the formatting this repo's CI checks.

On the watchOS 27.0 Simulator (Series 12, 46mm, 2026-10-30) the example
started with no error in its console (first frame 0.8 s after the engine
ran, built with `--dart-define=flutter.inspector.structuredErrors=false`),
and its list of test pages fits the screen. Its "Raw tests" page ran
upstream's SQL checks, against SQLite 3.54.0: every result seen while
scrolling the page was a pass, and the console showed no failure. The
official integration test passed (14 tests).

## Protocol coverage

Expand Down
11 changes: 7 additions & 4 deletions packages/sqflite_watchos/example/README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
# sqflite_example

A watchOS-only example: laps saved to a SQLite database on the watch. Run it
on an Apple Watch simulator with:
sqflite 2.4.5's own example app (`sqflite/example`), on watchOS: the test
pages of `sqflite_example_common`, which is not on pub.dev, so the pubspec
takes it from tekartik/sqflite at the commit of that release. Run it on an
Apple Watch simulator with:

```sh
flutter-watchos run
```

`integration_test/sqflite_test.dart` is sqflite's official integration test,
unchanged. It uses `package:test`'s `test`, which `flutter-watchos drive`
does not collect, so run it with `test`:
unchanged but for the formatting this repository's CI checks. It uses
`package:test`'s `test`, which `flutter-watchos drive` does not collect, so
run it with `test`:

```sh
flutter-watchos test integration_test/sqflite_test.dart -d <watch-sim>
Expand Down
29 changes: 29 additions & 0 deletions packages/sqflite_watchos/example/lib/compat_test_run.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'package:sqflite/sqflite.dart';

import 'package:sqflite_example_common/database/database.dart';

/// Test compatibility old API.
Future<void> testDebugModeCompat() async {
//await Sqflite.devSetDebugModeOn(false);
final path = await initDeleteDb('debug_mode.db');
final db = await openDatabase(path);
try {
// ignore: deprecated_member_use
final debugModeOn = await Sqflite.getDebugModeOn();
// ignore: deprecated_member_use
await Sqflite.setDebugModeOn(true);
await db.setVersion(1);
// ignore: deprecated_member_use
await Sqflite.setDebugModeOn(false);
// this message should not appear
await db.setVersion(2);
// ignore: deprecated_member_use
await Sqflite.setDebugModeOn(true);
await db.setVersion(3);
// restore
// ignore: deprecated_member_use
await Sqflite.setDebugModeOn(debugModeOn);
} finally {
await db.close();
}
}
114 changes: 4 additions & 110 deletions packages/sqflite_watchos/example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,112 +1,6 @@
// Copyright 2026 The FlutterWatch Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// sqflite's own example app is built on sqflite_example_common, which is not
// published, so this watch-sized demo stands in for it. The official
// integration test in integration_test/ is upstream's, unchanged.
import 'package:sqflite_example_common/main.dart';

import 'package:flutter/material.dart';
import 'package:sqflite/sqflite.dart';

void main() => runApp(const SqfliteExampleApp());

/// Laps saved to a SQLite database on the watch.
class SqfliteExampleApp extends StatelessWidget {
/// The example app.
const SqfliteExampleApp({super.key});

@override
Widget build(BuildContext context) => MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.dark(useMaterial3: true),
home: const LapsPage(),
);
}

/// Add, list and clear laps.
class LapsPage extends StatefulWidget {
/// The one page.
const LapsPage({super.key});

@override
State<LapsPage> createState() => _LapsPageState();
}

class _LapsPageState extends State<LapsPage> {
Database? _db;
List<Map<String, Object?>> _laps = const <Map<String, Object?>>[];
String? _error;

@override
void initState() {
super.initState();
_open();
}

@override
void dispose() {
_db?.close();
super.dispose();
}

Future<void> _open() async {
try {
_db = await openDatabase(
'laps.db',
version: 1,
onCreate: (Database db, int version) => db.execute(
'CREATE TABLE lap (id INTEGER PRIMARY KEY, at INTEGER NOT NULL)'),
);
await _reload();
} on DatabaseException catch (e) {
setState(() => _error = '$e');
}
}

Future<void> _reload() async {
final List<Map<String, Object?>> laps =
await _db!.query('lap', orderBy: 'id DESC');
setState(() => _laps = laps);
}

Future<void> _add() async {
await _db!.insert('lap', <String, Object?>{
'at': DateTime.now().millisecondsSinceEpoch,
});
await _reload();
}

Future<void> _clear() async {
await _db!.delete('lap');
await _reload();
}

String _time(Object? millis) {
final DateTime t = DateTime.fromMillisecondsSinceEpoch(millis! as int);
String two(int v) => v.toString().padLeft(2, '0');
return '${two(t.hour)}:${two(t.minute)}:${two(t.second)}';
}

@override
Widget build(BuildContext context) => Scaffold(
body: ListView(
padding: const EdgeInsets.fromLTRB(12, 28, 12, 24),
children: <Widget>[
Text('${_laps.length} laps',
style:
const TextStyle(fontSize: 24, fontWeight: FontWeight.w600)),
if (_error != null)
Text(_error!, style: const TextStyle(color: Colors.orangeAccent)),
FilledButton(
onPressed: _db == null ? null : _add,
child: const Text('Add lap'),
),
for (final Map<String, Object?> lap in _laps.take(5))
Text('#${lap['id']} ${_time(lap['at'])}'),
if (_laps.isNotEmpty)
TextButton(onPressed: _clear, child: const Text('Clear')),
],
),
);
Future<void> main() async {
supportsCompatMode = true;
mainExampleApp();
}
34 changes: 25 additions & 9 deletions packages/sqflite_watchos/example/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,28 +1,44 @@
name: sqflite_example
description: "watchOS example for sqflite_watchos."
publish_to: 'none'
version: 1.0.0+1
description: Demonstrates how to use the sqflite plugin and perform some tests.
publish_to: none
version: "0.4.0"

environment:
sdk: ">=3.1.0 <4.0.0"

# sqflite 2.4.5's own example. Each version is the one it ran with here, as
# a caret, since this repository's CI refuses an unconstrained example
# dependency. Two watch deviations: sqflite_example_common is not on
# pub.dev, so it is pinned to the tekartik/sqflite commit of sqflite 2.4.5,
# and sqflite_watchos is added.
dependencies:
path: ^1.9.1
collection: ^1.19.1
flutter:
sdk: flutter
sqflite: ^2.4.4
sqflite: ^2.4.5
sqflite_common: ^2.5.14
synchronized: ^3.5.0
matcher: ^0.12.20
meta: ^1.19.0
sqflite_example_common:
git:
url: https://github.com/tekartik/sqflite
path: packages_flutter/sqflite_example_common
ref: 21d2b8d0ab53159a73710db90857d928fad09c84
sqflite_watchos:
path: ../
path: ^1.9.0

dev_dependencies:
flutter_lints: ^6.0.0
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
flutter_lints: ^4.0.0
# The official integration test imports these two.
material_ui: ^1.5.0
test: ^1.25.0
test: ^1.31.1
process_run: ^1.4.1
pub_semver: ^2.2.1
material_ui: ^1.9.0

flutter:
uses-material-design: true
3 changes: 2 additions & 1 deletion tool/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,8 @@ that no longer matches. It moved here from
or `dev_dependencies`. An example built against whatever is newest that day
can break with no change in this repository. Use a caret constraint on the
version the example was last run with; `path:` and `sdk:` entries and
`dependency_overrides` are not checked.
`dependency_overrides` are not checked. A `git:` dependency must set `ref`
to a full commit hash: a branch, a tag or no `ref` can move the same way.

## Publish dry-run

Expand Down
53 changes: 47 additions & 6 deletions tool/lib/src/example_constraints.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,21 @@
/// flutter_secure_storage example resolved an upstream major its official
/// test no longer compiles against). No example pubspec may name `any`, or
/// leave a constraint empty, under `dependencies` or `dev_dependencies`.
///
/// A git dependency moves the same way unless its `ref` names one commit:
/// a branch, a tag or no `ref` at all can resolve to new code tomorrow. So a
/// git dependency must pin a full commit hash (sqflite's example takes
/// `sqflite_example_common`, which is not on pub.dev, from its repository).
library;

import 'dart:io';

import 'package:path/path.dart' as p;
import 'package:yaml/yaml.dart';

/// A full git commit hash, as `ref` must name it.
final RegExp _commitHash = RegExp(r'^[0-9a-f]{40}$');

/// The dependencies of the pubspec [contents] that name `any` or no
/// constraint, as `section: name`.
List<String> unconstrainedDependencies(String contents) {
Expand All @@ -35,6 +43,31 @@ List<String> unconstrainedDependencies(String contents) {
return found;
}

/// The git dependencies of the pubspec [contents] whose `ref` is not a full
/// commit hash, as `section: name`. A `git:` given as a plain URL has no
/// `ref`, so it is one of them.
List<String> unpinnedGitDependencies(String contents) {
final Object? pubspec = loadYaml(contents);
final List<String> found = <String>[];
for (final String section in <String>['dependencies', 'dev_dependencies']) {
final Object? dependencies = pubspec is YamlMap ? pubspec[section] : null;
if (dependencies is! YamlMap) {
continue;
}
dependencies.forEach((Object? name, Object? value) {
if (value is! YamlMap || !value.containsKey('git')) {
return;
}
final Object? git = value['git'];
final Object? ref = git is YamlMap ? git['ref'] : null;
if (ref is! String || !_commitHash.hasMatch(ref)) {
found.add('$section: $name');
}
});
}
return found;
}

/// Checks `packages/*/example/pubspec.yaml` of the repository at [root],
/// writes the report to [out] and returns the exit code.
int runExampleConstraintCheck(String root, StringSink out) {
Expand All @@ -55,21 +88,29 @@ int runExampleConstraintCheck(String root, StringSink out) {
continue;
}
examples++;
final List<String> found = unconstrainedDependencies(
pubspec.readAsStringSync(),
);
if (found.isEmpty) {
final String contents = pubspec.readAsStringSync();
final List<String> unconstrained = unconstrainedDependencies(contents);
final List<String> unpinned = unpinnedGitDependencies(contents);
if (unconstrained.isEmpty && unpinned.isEmpty) {
out.writeln('ok $package/example');
continue;
}
failures++;
out.writeln('FAIL $package/example');
for (final String dependency in found) {
for (final String dependency in unconstrained) {
out.writeln(' $dependency has no constraint; use ^x.y.z.');
}
for (final String dependency in unpinned) {
out.writeln(
' $dependency is a git dependency without a pinned commit; '
'set its ref to a full commit hash.',
);
}
}
if (failures > 0) {
out.writeln('$failures of $examples examples depend on "any".');
out.writeln(
'$failures of $examples examples have a dependency that can move.',
);
return 1;
}
out.writeln('Example constraints: all $examples examples pass.');
Expand Down
Loading
Loading