mirror of
https://github.com/flutter/samples.git
synced 2025-11-08 22:09:06 +00:00
[platform_channels] adds BasicMessageChannel Demo (#484)
This commit is contained in:
@@ -60,4 +60,5 @@ flutter {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
implementation 'com.google.code.gson:gson:2.8.6'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ package dev.flutter.platform_channels
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.hardware.Sensor
|
import android.hardware.Sensor
|
||||||
import android.hardware.SensorManager
|
import android.hardware.SensorManager
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.plugin.common.BasicMessageChannel
|
import io.flutter.plugin.common.*
|
||||||
import io.flutter.plugin.common.EventChannel
|
|
||||||
import io.flutter.plugin.common.MethodChannel
|
|
||||||
import io.flutter.plugin.common.StandardMessageCodec
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
class MainActivity : FlutterActivity() {
|
class MainActivity : FlutterActivity() {
|
||||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
@@ -50,7 +50,38 @@ class MainActivity : FlutterActivity() {
|
|||||||
.setMessageHandler { message, reply ->
|
.setMessageHandler { message, reply ->
|
||||||
if (message == "getImage") {
|
if (message == "getImage") {
|
||||||
val inputStream: InputStream = assets.open("eat_new_orleans.jpg")
|
val inputStream: InputStream = assets.open("eat_new_orleans.jpg")
|
||||||
reply.reply(inputStream.readBytes());
|
reply.reply(inputStream.readBytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val petList = mutableListOf<Map<String, String>>()
|
||||||
|
val gson = Gson()
|
||||||
|
|
||||||
|
// A BasicMessageChannel for sending petList to Dart.
|
||||||
|
val stringCodecChannel = BasicMessageChannel(flutterEngine.dartExecutor, "stringCodecDemo", StringCodec.INSTANCE)
|
||||||
|
|
||||||
|
// Registers a MessageHandler for BasicMessageChannel to receive pet details to be
|
||||||
|
// added in petList and send the it back to Dart using stringCodecChannel.
|
||||||
|
BasicMessageChannel(flutterEngine.dartExecutor, "jsonMessageCodecDemo", JSONMessageCodec.INSTANCE)
|
||||||
|
.setMessageHandler { message, reply ->
|
||||||
|
petList.add(0, gson.fromJson(message.toString(), object : TypeToken<Map<String, String>>() {}.type))
|
||||||
|
stringCodecChannel.send(gson.toJson(mapOf("petList" to petList)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Registers a MessageHandler for BasicMessageChannel to receive the index of pet
|
||||||
|
// details to be removed from the petList and send the petList back to Dart using
|
||||||
|
// stringCodecChannel. If the index is not in the range of petList, we send null
|
||||||
|
// back to Dart.
|
||||||
|
BasicMessageChannel(flutterEngine.dartExecutor, "binaryCodecDemo", BinaryCodec.INSTANCE)
|
||||||
|
.setMessageHandler { message, reply ->
|
||||||
|
val index = String(message!!.array()).toInt()
|
||||||
|
if (index >= 0 && index < petList.size) {
|
||||||
|
petList.removeAt(index)
|
||||||
|
val replyMessage = "Removed Successfully"
|
||||||
|
reply.reply(ByteBuffer.allocateDirect(replyMessage.toByteArray().size).put(replyMessage.toByteArray()))
|
||||||
|
stringCodecChannel.send(gson.toJson(mapOf("petList" to petList)))
|
||||||
|
} else {
|
||||||
|
reply.reply(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:platform_channels/src/add_pet_details.dart';
|
||||||
|
import 'package:platform_channels/src/pet_list_screen.dart';
|
||||||
import 'package:platform_channels/src/event_channel_demo.dart';
|
import 'package:platform_channels/src/event_channel_demo.dart';
|
||||||
import 'package:platform_channels/src/method_channel_demo.dart';
|
import 'package:platform_channels/src/method_channel_demo.dart';
|
||||||
import 'package:platform_channels/src/platform_image_demo.dart';
|
import 'package:platform_channels/src/platform_image_demo.dart';
|
||||||
@@ -19,6 +21,8 @@ class PlatformChannelSample extends StatelessWidget {
|
|||||||
'/methodChannelDemo': (context) => MethodChannelDemo(),
|
'/methodChannelDemo': (context) => MethodChannelDemo(),
|
||||||
'/eventChannelDemo': (context) => EventChannelDemo(),
|
'/eventChannelDemo': (context) => EventChannelDemo(),
|
||||||
'/platformImageDemo': (context) => PlatformImageDemo(),
|
'/platformImageDemo': (context) => PlatformImageDemo(),
|
||||||
|
'/petListScreen': (context) => PetListScreen(),
|
||||||
|
'/addPetDetails': (context) => AddPetDetails(),
|
||||||
},
|
},
|
||||||
title: 'Platform Channel Sample',
|
title: 'Platform Channel Sample',
|
||||||
home: HomePage(),
|
home: HomePage(),
|
||||||
@@ -47,6 +51,10 @@ List<DemoInfo> demoList = [
|
|||||||
DemoInfo(
|
DemoInfo(
|
||||||
'Platform Image Demo',
|
'Platform Image Demo',
|
||||||
'/platformImageDemo',
|
'/platformImageDemo',
|
||||||
|
),
|
||||||
|
DemoInfo(
|
||||||
|
'BasicMessageChannel Demo',
|
||||||
|
'/petListScreen',
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
86
platform_channels/lib/src/add_pet_details.dart
Normal file
86
platform_channels/lib/src/add_pet_details.dart
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
// Copyright 2020 The Flutter team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:platform_channels/src/pet_list_message_channel.dart';
|
||||||
|
|
||||||
|
/// Demonstrates how to use [BasicMessageChannel] to send a message to platform.
|
||||||
|
///
|
||||||
|
/// The widget uses [TextField] and [RadioListTile] to take the [PetDetails.breed] and
|
||||||
|
/// [PetDetails.petType] from the user respectively.
|
||||||
|
class AddPetDetails extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_AddPetDetailsState createState() => _AddPetDetailsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AddPetDetailsState extends State<AddPetDetails> {
|
||||||
|
final breedTextController = TextEditingController();
|
||||||
|
String petType = 'Dog';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('Add Pet Details'),
|
||||||
|
actions: <Widget>[
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.add),
|
||||||
|
onPressed: () {
|
||||||
|
PetListMessageChannel.addPetDetails(
|
||||||
|
PetDetails(
|
||||||
|
petType: petType,
|
||||||
|
breed: breedTextController.text,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
TextField(
|
||||||
|
controller: breedTextController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
filled: true,
|
||||||
|
hintText: 'Breed of pet',
|
||||||
|
labelText: 'Breed',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
RadioListTile<String>(
|
||||||
|
title: const Text('Dog'),
|
||||||
|
value: 'Dog',
|
||||||
|
groupValue: petType,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
petType = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
RadioListTile<String>(
|
||||||
|
title: const Text('Cat'),
|
||||||
|
value: 'Cat',
|
||||||
|
groupValue: petType,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
petType = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
84
platform_channels/lib/src/pet_list_message_channel.dart
Normal file
84
platform_channels/lib/src/pet_list_message_channel.dart
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
// Copyright 2020 The Flutter team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
/// This class includes two methods [addPetDetails] and [removePet] which are used
|
||||||
|
/// to add a new pet and remove a pet from the the list respectively.
|
||||||
|
class PetListMessageChannel {
|
||||||
|
static final _jsonMessageCodecChannel =
|
||||||
|
BasicMessageChannel<dynamic>('jsonMessageCodecDemo', JSONMessageCodec());
|
||||||
|
|
||||||
|
static final _binaryCodecChannel =
|
||||||
|
BasicMessageChannel('binaryCodecDemo', BinaryCodec());
|
||||||
|
|
||||||
|
/// Method to add a new pet to the list.
|
||||||
|
///
|
||||||
|
/// Demonstrates how to use [BasicMessageChannel] and [JSONMessageCodec] to
|
||||||
|
/// send more structured data to platform like a [Map] in this case.
|
||||||
|
static void addPetDetails(PetDetails petDetails) {
|
||||||
|
_jsonMessageCodecChannel.send(petDetails.toJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Method to remove a pet from the list.
|
||||||
|
///
|
||||||
|
/// Demonstrates how to use [BasicMessageChannel] and [BinaryCodec] to
|
||||||
|
/// send [ByteData] to platform. If the reply received is null, then
|
||||||
|
/// we will throw a [PlatformException].
|
||||||
|
static Future<void> removePet(int index) async {
|
||||||
|
final uInt8List = utf8.encoder.convert(index.toString());
|
||||||
|
final reply = await _binaryCodecChannel.send(uInt8List.buffer.asByteData());
|
||||||
|
if (reply == null) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'INVALID INDEX',
|
||||||
|
message: 'Failed to delete pet details',
|
||||||
|
details: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A model class that provides [petList] which is received from platform.
|
||||||
|
class PetListModel {
|
||||||
|
PetListModel({
|
||||||
|
this.petList,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<PetDetails> petList;
|
||||||
|
|
||||||
|
/// Method that maps the incoming string of json object to List of [PetDetails].
|
||||||
|
factory PetListModel.fromJson(String jsonString) {
|
||||||
|
final jsonData = json.decode(jsonString) as Map<String, dynamic>;
|
||||||
|
return PetListModel(
|
||||||
|
petList: List.from((jsonData['petList'] as List).map<PetDetails>(
|
||||||
|
(dynamic petDetailsMap) => PetDetails.fromMap(
|
||||||
|
petDetailsMap as Map<String, dynamic>,
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A simple model that provides pet details like [petType] and [breed] of pet.
|
||||||
|
class PetDetails {
|
||||||
|
PetDetails({
|
||||||
|
this.petType,
|
||||||
|
this.breed,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String petType;
|
||||||
|
final String breed;
|
||||||
|
|
||||||
|
factory PetDetails.fromMap(Map<String, dynamic> map) => PetDetails(
|
||||||
|
petType: map['petType'] as String,
|
||||||
|
breed: map['breed'] as String,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, String> toJson() => <String, String>{
|
||||||
|
'petType': petType,
|
||||||
|
'breed': breed,
|
||||||
|
};
|
||||||
|
}
|
||||||
91
platform_channels/lib/src/pet_list_screen.dart
Normal file
91
platform_channels/lib/src/pet_list_screen.dart
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
// Copyright 2020 The Flutter team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:platform_channels/src/pet_list_message_channel.dart';
|
||||||
|
|
||||||
|
/// Demonstrates how to use [BasicMessageChannel] to send & receive the platform
|
||||||
|
/// Message.
|
||||||
|
class PetListScreen extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
_PetListScreenState createState() => _PetListScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PetListScreenState extends State<PetListScreen> {
|
||||||
|
PetListModel petListModel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
// Receives a string of json object from the platform and converts it
|
||||||
|
// to PetModel.
|
||||||
|
BasicMessageChannel('stringCodecDemo', StringCodec())
|
||||||
|
.setMessageHandler((message) async {
|
||||||
|
setState(() {
|
||||||
|
petListModel = PetListModel.fromJson(message);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('Pet List'),
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
child: Icon(Icons.add),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pushNamed(context, '/addPetDetails');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
body: petListModel?.petList?.isEmpty ?? true
|
||||||
|
? Center(child: Text('Enter Pet Details'))
|
||||||
|
: BuildPetList(petListModel.petList),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shows list of [PetDetails].
|
||||||
|
class BuildPetList extends StatelessWidget {
|
||||||
|
final List<PetDetails> petList;
|
||||||
|
|
||||||
|
BuildPetList(this.petList);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListView.builder(
|
||||||
|
padding: EdgeInsets.all(8),
|
||||||
|
itemCount: petList.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text('Pet breed: ${petList[index].breed}'),
|
||||||
|
subtitle: Text(
|
||||||
|
'Pet type: ${petList[index].petType}',
|
||||||
|
),
|
||||||
|
trailing: IconButton(
|
||||||
|
icon: Icon(Icons.delete),
|
||||||
|
onPressed: () async {
|
||||||
|
try {
|
||||||
|
await PetListMessageChannel.removePet(index);
|
||||||
|
showSnackBar('Removed successfully!', context);
|
||||||
|
} catch (error) {
|
||||||
|
showSnackBar(error.message.toString(), context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showSnackBar(String message, BuildContext context) {
|
||||||
|
Scaffold.of(context).showSnackBar(SnackBar(
|
||||||
|
backgroundColor: Theme.of(context).primaryColor,
|
||||||
|
content: Text(message),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
37
platform_channels/test/src/add_pet_details_test.dart
Normal file
37
platform_channels/test/src/add_pet_details_test.dart
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2020 The Flutter team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:platform_channels/src/add_pet_details.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('AddPetDetails tests', () {
|
||||||
|
var petList = <Map>[];
|
||||||
|
|
||||||
|
setUpAll(() {
|
||||||
|
BasicMessageChannel<dynamic>('jsonMessageCodecDemo', JSONMessageCodec())
|
||||||
|
.setMockMessageHandler((dynamic message) async {
|
||||||
|
petList.add(message as Map);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Enter pet details', (tester) async {
|
||||||
|
await tester.pumpWidget(MaterialApp(home: AddPetDetails()));
|
||||||
|
|
||||||
|
// Enter the breed of cat.
|
||||||
|
await tester.enterText(find.byType(TextField), 'Persian');
|
||||||
|
// Select cat from the pet type.
|
||||||
|
await tester.tap(find.text('Cat'));
|
||||||
|
|
||||||
|
// Initially the list will be empty.
|
||||||
|
expect(petList, isEmpty);
|
||||||
|
await tester.tap(find.byIcon(Icons.add));
|
||||||
|
|
||||||
|
expect(petList, isNotEmpty);
|
||||||
|
expect(petList.last['breed'], 'Persian');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
83
platform_channels/test/src/pet_list_screen_test.dart
Normal file
83
platform_channels/test/src/pet_list_screen_test.dart
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// Copyright 2020 The Flutter team. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:platform_channels/src/pet_list_message_channel.dart';
|
||||||
|
import 'package:platform_channels/src/pet_list_screen.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('PetListScreen tests', () {
|
||||||
|
final basicMessageChannel =
|
||||||
|
BasicMessageChannel('stringCodecDemo', StringCodec());
|
||||||
|
|
||||||
|
var petList = [
|
||||||
|
{
|
||||||
|
'petType': 'Dog',
|
||||||
|
'breed': 'Pug',
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
PetListModel petListModel;
|
||||||
|
|
||||||
|
setUpAll(() {
|
||||||
|
// Mock for the pet list received from the platform.
|
||||||
|
basicMessageChannel.setMockMessageHandler((message) async {
|
||||||
|
petListModel = PetListModel.fromJson(message);
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock for the index received from the Dart to delete the pet details,
|
||||||
|
// and send the updated pet list back to Dart.
|
||||||
|
BasicMessageChannel('binaryCodecDemo', BinaryCodec())
|
||||||
|
.setMockMessageHandler((message) async {
|
||||||
|
// Convert the ByteData to String.
|
||||||
|
final index = utf8.decoder.convert(message.buffer
|
||||||
|
.asUint8List(message.offsetInBytes, message.lengthInBytes));
|
||||||
|
|
||||||
|
// Remove the pet details at the given index.
|
||||||
|
petList.removeAt(int.parse(index));
|
||||||
|
|
||||||
|
// Send the updated petList back.
|
||||||
|
final map = {'petList': petList};
|
||||||
|
await basicMessageChannel.send(json.encode(map));
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('convert json message to PetListModel', () {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
// Initially petListModel will be null.
|
||||||
|
expect(petListModel, isNull);
|
||||||
|
|
||||||
|
// Send the pet list using BasicMessageChannel.
|
||||||
|
final map = {'petList': petList};
|
||||||
|
basicMessageChannel.send(json.encode(map));
|
||||||
|
|
||||||
|
// Get the details of first pet.
|
||||||
|
final petDetails = petListModel.petList.first;
|
||||||
|
expect(petDetails.petType, 'Dog');
|
||||||
|
expect(petDetails.breed, 'Pug');
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('BuildPetList test', (tester) async {
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: BuildPetList(petListModel.petList),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(find.text('Pet type: Dog'), findsOneWidget);
|
||||||
|
expect(find.text('Pet breed: Pug'), findsOneWidget);
|
||||||
|
|
||||||
|
// Delete the pet details.
|
||||||
|
await tester.tap(find.byIcon(Icons.delete).first);
|
||||||
|
expect(petListModel.petList, isEmpty);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user