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