[platform_channels] adds BasicMessageChannel Demo (#484)

pull/493/head
Ayush Bherwani 5 years ago committed by GitHub
parent 9140559040
commit 03008a5d19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -60,4 +60,5 @@ flutter {
dependencies {
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.hardware.Sensor
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.engine.FlutterEngine
import io.flutter.plugin.common.BasicMessageChannel
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.common.*
import java.io.InputStream
import java.nio.ByteBuffer
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
@ -50,7 +50,38 @@ class MainActivity : FlutterActivity() {
.setMessageHandler { message, reply ->
if (message == "getImage") {
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.
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/method_channel_demo.dart';
import 'package:platform_channels/src/platform_image_demo.dart';
@ -19,6 +21,8 @@ class PlatformChannelSample extends StatelessWidget {
'/methodChannelDemo': (context) => MethodChannelDemo(),
'/eventChannelDemo': (context) => EventChannelDemo(),
'/platformImageDemo': (context) => PlatformImageDemo(),
'/petListScreen': (context) => PetListScreen(),
'/addPetDetails': (context) => AddPetDetails(),
},
title: 'Platform Channel Sample',
home: HomePage(),
@ -47,6 +51,10 @@ List<DemoInfo> demoList = [
DemoInfo(
'Platform Image Demo',
'/platformImageDemo',
),
DemoInfo(
'BasicMessageChannel Demo',
'/petListScreen',
)
];

@ -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…
Cancel
Save