diff --git a/packages/shorebird_cli/lib/src/commands/preview_command.dart b/packages/shorebird_cli/lib/src/commands/preview_command.dart index 220b22af..f4cfd689 100644 --- a/packages/shorebird_cli/lib/src/commands/preview_command.dart +++ b/packages/shorebird_cli/lib/src/commands/preview_command.dart @@ -247,6 +247,7 @@ class PreviewCommand extends ShorebirdCommand { final startAppProgress = logger.progress('Starting app'); try { + await adb.clearAppData(package: package, deviceId: deviceId); await adb.startApp(package: package, deviceId: deviceId); startAppProgress.complete(); } catch (error) { diff --git a/packages/shorebird_cli/lib/src/executables/adb.dart b/packages/shorebird_cli/lib/src/executables/adb.dart index c02db702..3024b90c 100644 --- a/packages/shorebird_cli/lib/src/executables/adb.dart +++ b/packages/shorebird_cli/lib/src/executables/adb.dart @@ -27,6 +27,21 @@ class Adb { return process.start(adbPath, command.split(' ')); } + /// Clears the app data for the given [package] name. + Future clearAppData({required String package, String? deviceId}) async { + final args = [ + if (deviceId != null) ...['-s', deviceId], + 'shell', + 'pm', + 'clear', + package, + ]; + final result = await _exec(args.join(' ')); + if (result.exitCode != 0) { + throw Exception('Unable to clear app data: ${result.stderr}'); + } + } + /// Starts the app with the given [package] name. Future startApp({ required String package, diff --git a/packages/shorebird_cli/lib/src/executables/ios_deploy.dart b/packages/shorebird_cli/lib/src/executables/ios_deploy.dart index f071beab..26590a86 100644 --- a/packages/shorebird_cli/lib/src/executables/ios_deploy.dart +++ b/packages/shorebird_cli/lib/src/executables/ios_deploy.dart @@ -160,6 +160,7 @@ Or run on an iOS simulator without code signing [ '--debug', if (deviceId != null) ...['--id', deviceId], + '-r', // uninstall the app before reinstalling and clear app data '--bundle', bundlePath, ], diff --git a/packages/shorebird_cli/test/src/commands/preview_command_test.dart b/packages/shorebird_cli/test/src/commands/preview_command_test.dart index 9f0b0dfe..5bebb29c 100644 --- a/packages/shorebird_cli/test/src/commands/preview_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/preview_command_test.dart @@ -247,6 +247,12 @@ void main() { deviceId: any(named: 'deviceId'), ), ).thenAnswer((_) async {}); + when( + () => adb.clearAppData( + package: any(named: 'package'), + deviceId: any(named: 'deviceId'), + ), + ).thenAnswer((_) async {}); when( () => adb.startApp( package: any(named: 'package'), @@ -367,6 +373,23 @@ void main() { verify(() => bundletool.installApks(apks: apksPath())).called(1); }); + test('exits with code 70 when clearing app data fails', () async { + when( + () => artifactManager.extractZip( + zipFile: any(named: 'zipFile'), + outputDirectory: any(named: 'outputDirectory'), + ), + ).thenAnswer(createShorebirdYaml); + + final exception = Exception('oops'); + when( + () => adb.clearAppData(package: any(named: 'package')), + ).thenThrow(exception); + final result = await runWithOverrides(command.run); + expect(result, equals(ExitCode.software.code)); + verify(() => adb.clearAppData(package: packageName)).called(1); + }); + test('exits with code 70 when starting app fails', () async { when( () => artifactManager.extractZip( diff --git a/packages/shorebird_cli/test/src/executables/adb_test.dart b/packages/shorebird_cli/test/src/executables/adb_test.dart index c36663e1..5128086f 100644 --- a/packages/shorebird_cli/test/src/executables/adb_test.dart +++ b/packages/shorebird_cli/test/src/executables/adb_test.dart @@ -48,6 +48,71 @@ void main() { ); }); + group('clearAppData', () { + const package = 'com.example.app'; + test('throws when unable to locate adb', () async { + when(() => androidSdk.adbPath).thenReturn(null); + await expectLater( + () => runWithOverrides(() => adb.clearAppData(package: package)), + throwsA( + isA().having( + (e) => e.toString(), + 'exception()', + contains('Unable to locate adb'), + ), + ), + ); + }); + + test('throws process exits with non-zero exit code', () async { + when( + () => process.run(any(), any()), + ).thenAnswer( + (_) async => const ShorebirdProcessResult( + exitCode: 1, + stdout: '', + stderr: 'oops', + ), + ); + await expectLater( + () => runWithOverrides(() => adb.clearAppData(package: package)), + throwsA( + isA().having( + (e) => e.toString(), + 'exception()', + contains('Unable to clear app data: oops'), + ), + ), + ); + }); + + test('completes when process exits with 0', () async { + await expectLater( + runWithOverrides(() => adb.clearAppData(package: package)), + completes, + ); + verify( + () => process.run(adbPath, 'shell pm clear $package'.split(' ')), + ).called(1); + }); + + test('forwards deviceId when provided', () async { + const deviceId = '1234'; + await expectLater( + runWithOverrides( + () => adb.clearAppData(package: package, deviceId: deviceId), + ), + completes, + ); + verify( + () => process.run( + adbPath, + '-s $deviceId shell pm clear $package'.split(' '), + ), + ).called(1); + }); + }); + group('startApp', () { const package = 'com.example.app'; test('throws when unable to locate adb', () async { diff --git a/packages/shorebird_cli/test/src/executables/ios_deploy_test.dart b/packages/shorebird_cli/test/src/executables/ios_deploy_test.dart index cdaa3d88..f04764a8 100644 --- a/packages/shorebird_cli/test/src/executables/ios_deploy_test.dart +++ b/packages/shorebird_cli/test/src/executables/ios_deploy_test.dart @@ -93,6 +93,7 @@ void main() { '--debug', '--id', deviceId, + '-r', '--bundle', bundlePath, ]), @@ -113,6 +114,7 @@ void main() { verify( () => shorebirdProcess.start(any(that: endsWith('ios-deploy')), [ '--debug', + '-r', '--bundle', bundlePath, ]), @@ -132,6 +134,7 @@ void main() { verify( () => shorebirdProcess.start(any(that: endsWith('ios-deploy')), [ '--debug', + '-r', '--bundle', bundlePath, ]), @@ -157,6 +160,7 @@ void main() { verify( () => shorebirdProcess.start(any(that: endsWith('ios-deploy')), [ '--debug', + '-r', '--bundle', bundlePath, ]), @@ -183,6 +187,7 @@ void main() { verify( () => shorebirdProcess.start(any(that: endsWith('ios-deploy')), [ '--debug', + '-r', '--bundle', bundlePath, ]), @@ -207,6 +212,7 @@ void main() { verify( () => shorebirdProcess.start(any(that: endsWith('ios-deploy')), [ '--debug', + '-r', '--bundle', bundlePath, ]), @@ -232,6 +238,7 @@ void main() { verify( () => shorebirdProcess.start(any(that: endsWith('ios-deploy')), [ '--debug', + '-r', '--bundle', bundlePath, ]), @@ -257,6 +264,7 @@ void main() { verify( () => shorebirdProcess.start(any(that: endsWith('ios-deploy')), [ '--debug', + '-r', '--bundle', bundlePath, ]), @@ -287,6 +295,7 @@ void main() { verify( () => shorebirdProcess.start(any(that: endsWith('ios-deploy')), [ '--debug', + '-r', '--bundle', bundlePath, ]), @@ -312,6 +321,7 @@ void main() { verify( () => shorebirdProcess.start(any(that: endsWith('ios-deploy')), [ '--debug', + '-r', '--bundle', bundlePath, ]), @@ -336,6 +346,7 @@ void main() { verify( () => shorebirdProcess.start(any(that: endsWith('ios-deploy')), [ '--debug', + '-r', '--bundle', bundlePath, ]),