Apply Flutter best practices [Non-Breaking] (#2)

* Add flutter_lints package

* Use key in widget constructors

lint: use_key_in_widget_constructors

* Unnecessary new keyword

lint: unnecessary_new

* Prefer const with constant constructors

* Exclude protobuf files from analyzer

* Private field could be final

* Annotate overridden members

* Use initializing formals when possible

* Don't access members with `this` unless avoiding shadowing

* Prefer is! operator

* Use isEmpty instead of length

* Use collection literals when possible

* Revert comment changes

* Cleaner null testing

* Misc fixes

* Avoid using `forEach` with a function literal

* Prefer `final` over `var` where applicable

* Enforce stricter type-checking

* Ignore VS Code files

* Flutter format

* Prefer using lowerCamelCase for constant names

* flutter format -l 100

* Disable `avoid_print` for examples

* Slight improvements to example

Should solve lint error: no_logic_in_create_state
This commit is contained in:
Hiroshi Horie
2021-08-31 16:35:07 +09:00
committed by GitHub
parent 4a75d78ef9
commit ab31ddabb9
41 changed files with 904 additions and 1228 deletions
+83 -58
View File
@@ -10,88 +10,113 @@ void main() {
print('${record.level.name}: ${record.time}: ${record.message}');
});
runApp(MyApp());
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
//
const MyApp({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'LiveKit Demo',
theme: ThemeData(
primarySwatch: Colors.deepPurple,
),
home: PreConnect(),
);
}
Widget build(BuildContext context) => MaterialApp(
title: 'LiveKit Demo',
theme: ThemeData(
primarySwatch: Colors.deepPurple,
),
home: const PreConnectWidget(
url: '<livekit_host>',
token: '<access_token>',
),
);
}
class PreConnect extends StatefulWidget {
class PreConnectWidget extends StatefulWidget {
//
final String url;
final String token;
const PreConnectWidget({
required this.url,
required this.token,
Key? key,
}) : super(key: key);
@override
State<StatefulWidget> createState() {
return _PreConnectState(
'<livekit_host>',
'<access_token>',
);
}
State<StatefulWidget> createState() => _PreConnectWidgetState();
}
class _PreConnectState extends State<PreConnect> {
String url;
String token;
class _PreConnectWidgetState extends State<PreConnectWidget> {
//
final _urlCtrl = TextEditingController();
final _tokenCtrl = TextEditingController();
_PreConnectState(this.url, this.token);
@override
void initState() {
super.initState();
_urlCtrl.text = widget.url;
_tokenCtrl.text = widget.token;
}
_connect(BuildContext context) async {
@override
void dispose() {
_urlCtrl.dispose();
_tokenCtrl.dispose();
super.dispose();
}
void _connect(BuildContext context) async {
try {
var room = await LiveKitClient.connect(this.url, this.token);
Navigator.push(
print('Connecting with url: ${_urlCtrl.text}, token: ${_tokenCtrl.text}...');
final room = await LiveKitClient.connect(
_urlCtrl.text,
_tokenCtrl.text,
);
Navigator.push<void>(
context,
MaterialPageRoute(builder: (context) {
return RoomWidget(room);
}),
);
} catch (e) {
print("could not connect $e");
print('could not connect $e');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Connect to LiveKit'),
),
body: Center(
child: Container(
// width: 250,
alignment: Alignment.center,
margin: const EdgeInsets.all(10),
child: Column(
children: [
TextFormField(
decoration: const InputDecoration(
labelText: 'URL',
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: const Text('Connect to LiveKit'),
),
body: Center(
child: Container(
// width: 250,
alignment: Alignment.center,
margin: const EdgeInsets.all(10),
child: Column(
children: [
TextField(
controller: _urlCtrl,
decoration: const InputDecoration(
labelText: 'URL',
),
),
onChanged: (value) => this.url,
initialValue: this.url,
),
TextFormField(
decoration: const InputDecoration(
labelText: 'Token',
TextField(
controller: _tokenCtrl,
decoration: const InputDecoration(
labelText: 'Token',
),
),
onChanged: (value) => this.token,
initialValue: this.token,
),
TextButton(
onPressed: () => _connect(context),
child: Text('Connect'),
),
],
TextButton(
onPressed: () => _connect(context),
child: const Text('Connect'),
),
],
),
),
),
),
);
}
);
}
+32 -33
View File
@@ -4,9 +4,13 @@ import 'package:livekit_example/src/controls.dart';
import 'package:provider/provider.dart';
class RoomWidget extends StatefulWidget {
//
final Room room;
RoomWidget(this.room);
const RoomWidget(
this.room, {
Key? key,
}) : super(key: key);
@override
State<StatefulWidget> createState() {
@@ -32,16 +36,16 @@ class _RoomState extends State<RoomWidget> with RoomDelegate {
super.dispose();
}
_onConnected() async {
void _onConnected() async {
// video will fail when running in ios simulator
try {
var localVideo = await LocalVideoTrack.createCameraTrack();
final localVideo = await LocalVideoTrack.createCameraTrack();
await widget.room.localParticipant.publishVideoTrack(localVideo);
} catch (e) {
print('could not publish video: $e');
}
var localAudio = await LocalAudioTrack.createTrack();
final localAudio = await LocalAudioTrack.createTrack();
await widget.room.localParticipant.publishAudioTrack(localAudio);
sortParticipants();
}
@@ -65,14 +69,9 @@ class _RoomState extends State<RoomWidget> with RoomDelegate {
}
// last spoken at
var aSpokeAt = a.lastSpokeAt?.millisecondsSinceEpoch;
var bSpokeAt = b.lastSpokeAt?.millisecondsSinceEpoch;
if (aSpokeAt == null) {
aSpokeAt = 0;
}
if (bSpokeAt == null) {
bSpokeAt = 0;
}
final aSpokeAt = a.lastSpokeAt?.millisecondsSinceEpoch ?? 0;
final bSpokeAt = b.lastSpokeAt?.millisecondsSinceEpoch ?? 0;
if (aSpokeAt != bSpokeAt) {
return aSpokeAt > bSpokeAt ? -1 : 1;
}
@@ -83,8 +82,7 @@ class _RoomState extends State<RoomWidget> with RoomDelegate {
}
// joinedAt
return a.joinedAt.millisecondsSinceEpoch -
b.joinedAt.millisecondsSinceEpoch;
return a.joinedAt.millisecondsSinceEpoch - b.joinedAt.millisecondsSinceEpoch;
});
if (participants.length > 1) {
@@ -99,8 +97,8 @@ class _RoomState extends State<RoomWidget> with RoomDelegate {
@override
void onDisconnected() {
var context = _lastContext;
print("disconnected: $context");
final context = _lastContext;
print('disconnected: $context');
if (context != null) {
Navigator.pop(context);
}
@@ -110,8 +108,8 @@ class _RoomState extends State<RoomWidget> with RoomDelegate {
Widget build(BuildContext context) {
_lastContext = context;
var mainWidgets = <Widget>[];
var participants = this.participants;
final mainWidgets = <Widget>[];
final participants = this.participants;
if (participants.isNotEmpty) {
mainWidgets.add(Expanded(child: VideoView(participants.first)));
} else {
@@ -119,7 +117,7 @@ class _RoomState extends State<RoomWidget> with RoomDelegate {
}
if (participants.length > 1) {
var videoList = ListView.builder(
final videoList = ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: participants.length - 1,
itemBuilder: (BuildContext context, int index) {
@@ -127,12 +125,11 @@ class _RoomState extends State<RoomWidget> with RoomDelegate {
width: 100,
height: 60,
padding: const EdgeInsets.all(2),
child:
VideoView(participants[index + 1], quality: VideoQuality.LOW),
child: VideoView(participants[index + 1], quality: VideoQuality.LOW),
);
},
);
mainWidgets.add(Container(
mainWidgets.add(SizedBox(
height: 60,
child: videoList,
));
@@ -157,11 +154,15 @@ class _RoomState extends State<RoomWidget> with RoomDelegate {
// displays a participant in view
class VideoView extends StatefulWidget {
//
final Participant participant;
final VideoQuality quality;
VideoView(this.participant, {VideoQuality quality = VideoQuality.MEDIUM})
: this.quality = quality;
const VideoView(
this.participant, {
this.quality = VideoQuality.MEDIUM,
Key? key,
}) : super(key: key);
@override
State<StatefulWidget> createState() {
@@ -175,13 +176,13 @@ class _VideoViewState extends State<VideoView> with ParticipantDelegate {
@override
void initState() {
super.initState();
widget.participant.addListener(this._onParticipantChanged);
widget.participant.addListener(_onParticipantChanged);
_onParticipantChanged();
}
@override
void dispose() {
widget.participant.removeListener(this._onParticipantChanged);
widget.participant.removeListener(_onParticipantChanged);
super.dispose();
}
@@ -195,14 +196,12 @@ class _VideoViewState extends State<VideoView> with ParticipantDelegate {
// register for change so Flutter will re-build the widget upon change
void _onParticipantChanged() {
var subscribedVideos = widget.participant.videoTracks.values.where((pub) {
return pub.kind == TrackType.VIDEO &&
!pub.isScreenShare &&
pub.subscribed;
final subscribedVideos = widget.participant.videoTracks.values.where((pub) {
return pub.kind == TrackType.VIDEO && !pub.isScreenShare && pub.subscribed;
});
setState(() {
if (subscribedVideos.isNotEmpty) {
var videoPub = subscribedVideos.first;
final videoPub = subscribedVideos.first;
if (videoPub is RemoteTrackPublication) {
videoPub.videoQuality = widget.quality;
}
@@ -212,13 +211,13 @@ class _VideoViewState extends State<VideoView> with ParticipantDelegate {
return;
}
}
this.videoPub = null;
videoPub = null;
});
}
@override
Widget build(BuildContext context) {
var videoPub = this.videoPub;
final videoPub = this.videoPub;
if (videoPub != null) {
return VideoTrackRenderer(videoPub.track as VideoTrack);
} else {
+25 -20
View File
@@ -2,10 +2,15 @@ import 'package:flutter/material.dart';
import 'package:livekit_client/livekit_client.dart';
class Controls extends StatefulWidget {
//
final Room room;
final LocalParticipant participant;
Controls(this.room) : participant = room.localParticipant;
Controls(
this.room, {
Key? key,
}) : participant = room.localParticipant,
super(key: key);
@override
State<StatefulWidget> createState() {
@@ -14,7 +19,7 @@ class Controls extends StatefulWidget {
}
class _ControlsState extends State<Controls> {
CameraPosition position = CameraPosition.FRONT;
CameraPosition position = CameraPosition.front;
@override
void initState() {
@@ -30,48 +35,48 @@ class _ControlsState extends State<Controls> {
LocalParticipant get participant => widget.participant;
_onChange() {
void _onChange() {
// trigger refresh
setState(() {});
}
_muteAudio() {
void _muteAudio() {
if (participant.hasAudio) {
var audioPub = participant.audioTracks.values.first;
final audioPub = participant.audioTracks.values.first;
audioPub.muted = true;
}
}
_unmuteAudio() async {
Future<void> _unmuteAudio() async {
if (participant.hasAudio) {
var audioPub = participant.audioTracks.values.first;
final audioPub = participant.audioTracks.values.first;
audioPub.muted = false;
} else {
// publish audio track
var audioTrack = await LocalAudioTrack.createTrack();
final audioTrack = await LocalAudioTrack.createTrack();
await participant.publishAudioTrack(audioTrack);
}
}
_muteVideo() {
void _muteVideo() {
if (participant.hasVideo) {
var videoPub = participant.videoTracks.values.first;
final videoPub = participant.videoTracks.values.first;
videoPub.muted = true;
}
}
_unmuteVideo() async {
void _unmuteVideo() async {
if (participant.hasVideo) {
var videoPub = participant.videoTracks.values.first;
final videoPub = participant.videoTracks.values.first;
videoPub.muted = false;
} else {
// publish audio track
var videoTrack = await LocalVideoTrack.createCameraTrack();
final videoTrack = await LocalVideoTrack.createCameraTrack();
await participant.publishVideoTrack(videoTrack);
}
}
_setCameraPosition(TrackPublication? pub, CameraPosition position) async {
void _setCameraPosition(TrackPublication? pub, CameraPosition position) async {
if (this.position == position) {
return;
}
@@ -96,13 +101,13 @@ class _ControlsState extends State<Controls> {
});
}
_exit() {
void _exit() {
widget.room.disconnect();
}
@override
Widget build(BuildContext context) {
var buttons = <Widget>[];
final buttons = <Widget>[];
// mute audio
if (participant.hasAudio && !participant.isMuted) {
@@ -127,7 +132,7 @@ class _ControlsState extends State<Controls> {
videoPub = participant.videoTracks.values.first;
}
var videoEnabled = videoPub != null && !videoPub.muted;
final videoEnabled = videoPub != null && !videoPub.muted;
if (videoEnabled) {
buttons.add(IconButton(
onPressed: _muteVideo,
@@ -140,12 +145,12 @@ class _ControlsState extends State<Controls> {
));
}
if (position == CameraPosition.FRONT) {
if (position == CameraPosition.front) {
buttons.add(IconButton(
icon: const Icon(Icons.video_camera_front_rounded),
onPressed: videoEnabled
? () {
_setCameraPosition(videoPub, CameraPosition.BACK);
_setCameraPosition(videoPub, CameraPosition.back);
}
: null,
));
@@ -154,7 +159,7 @@ class _ControlsState extends State<Controls> {
icon: const Icon(Icons.video_camera_back_rounded),
onPressed: videoEnabled
? () {
_setCameraPosition(videoPub, CameraPosition.FRONT);
_setCameraPosition(videoPub, CameraPosition.front);
}
: null,
));