mirror of https://github.com/flutter/samples.git
[platform_channels] adds BasicMessageChannel Demo (#484)
parent
9140559040
commit
03008a5d19
@ -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;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
};
|
||||
}
|
@ -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),
|
||||
));
|
||||
}
|
||||
}
|
@ -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');
|
||||
});
|
||||
});
|
||||
}
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Reference in new issue