Skip to content

Commit

Permalink
Fix app UI latency due to MethodChannels by adding timeStamps
Browse files Browse the repository at this point in the history
  • Loading branch information
josephnglynn committed Mar 9, 2024
1 parent abf49c7 commit 0508543
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,8 @@ class MainActivity : FlutterActivity() {
private val tickReceiver =
object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val secondsLeft = intent.getIntExtra("secondsLeft", 0)
val secondsTotal = intent.getIntExtra("secondsTotal", 1)
channel?.invokeMethod("tick", intArrayOf(secondsLeft, secondsTotal))
val result = timerService?.timer?.generateMethodChannelPayload()
channel?.invokeMethod("tick", result)
}
}

Expand Down
8 changes: 8 additions & 0 deletions android/app/src/main/kotlin/com/presley/flexify/Timer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ class Timer(private var msTimerDuration: Long) {
return true
}

fun generateMethodChannelPayload(): LongArray {
return longArrayOf(
totalTimerDuration,
totalTimerDuration - getRemainingMillis(),
java.lang.System.currentTimeMillis()
)
}

private fun requestPermission(context: Context): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return true
val intent = Intent(ACTION_REQUEST_SCHEDULE_EXACT_ALARM)
Expand Down
10 changes: 3 additions & 7 deletions android/app/src/main/kotlin/com/presley/flexify/TimerService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,10 @@ class TimerService : Service() {
}

private fun sendTickBroadcast() {
sendBroadcast(
Intent(MainActivity.TICK_BROADCAST)
.putExtra("secondsLeft", timer.getRemainingSeconds())
.putExtra("secondsTotal", timer.getDurationSeconds())
)
sendBroadcast(Intent(MainActivity.TICK_BROADCAST))
}

private fun onTimerExpired(intent: Intent?) {
private fun onTimerExpired() {
Log.d("TimerService", "onTimerExpired duration=${timer.getDurationSeconds()}")
timer.expire()
vibrate()
Expand Down Expand Up @@ -169,7 +165,7 @@ class TimerService : Service() {
}

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent != null && intent.action == TIMER_EXPIRED) onTimerExpired(intent)
if (intent != null && intent.action == TIMER_EXPIRED) onTimerExpired()
else onTimerStart(intent);
return START_STICKY
}
Expand Down
30 changes: 18 additions & 12 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:flexify/timer.dart';
import 'package:flexify/database.dart';
import 'package:flexify/graphs_page.dart';
import 'package:flutter/material.dart';
Expand All @@ -11,17 +12,15 @@ late MethodChannel android;

class AppState extends ChangeNotifier {
String? selectedExercise;
int? secondsLeft;
int? secondsTotal;
Timer timer = Timer.emptyTimer();

void selectExercise(String exercise) {
selectedExercise = exercise;
notifyListeners();
}

void updateSeconds(int left, int total) {
secondsLeft = left;
secondsTotal = total;
void updateTimer(Timer newTimer) {
timer = newTimer;
notifyListeners();
}
}
Expand Down Expand Up @@ -87,10 +86,16 @@ class _MyHomePageState extends State<MyHomePage>
Widget build(BuildContext context) {
android.setMethodCallHandler((call) async {
if (call.method == 'tick') {
int left = call.arguments[0];
int total = call.arguments[1];
Provider.of<AppState>(context, listen: false)
.updateSeconds(left, total);
final newTimer = Timer(
Duration(milliseconds: call.arguments[0]),
Duration(milliseconds: call.arguments[1]),
DateTime.fromMillisecondsSinceEpoch(call.arguments[2], isUtc: true),
);

Provider.of<AppState>(
context,
listen: false,
).updateTimer(newTimer);
}
});

Expand All @@ -100,13 +105,14 @@ class _MyHomePageState extends State<MyHomePage>
builder: (BuildContext context) {
return Scaffold(
bottomSheet: Consumer<AppState>(builder: (context, value, child) {
final duration = value.secondsTotal ?? 0;
final elapsed = duration - (value.secondsLeft ?? 0);
final duration = value.timer.getDuration().inSeconds;
final elapsed = value.timer.getElapsed().inSeconds;

return Visibility(
visible: duration > 0,
child: LinearProgressIndicator(
value: duration == 0 ? 0 : elapsed / duration),
value: duration == 0 ? 0 : elapsed / duration,
),
);
}),
body: SafeArea(
Expand Down
27 changes: 13 additions & 14 deletions lib/start_plan_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ class _StartPlanPageState extends State<StartPlanPage> {
super.dispose();
}


void selectWeight() {
weightController.selection = TextSelection(
baseOffset: 0,
Expand Down Expand Up @@ -135,24 +134,19 @@ class _StartPlanPageState extends State<StartPlanPage> {
final permission = await Permission.notification.request();
if (!permission.isGranted) return;
// 3s 3m30s
android.invokeMethod('timer', [210000, exercise]);
const duration = Duration(minutes: 3, seconds: 30);
android.invokeMethod('timer', [duration.inMilliseconds, exercise]);

if (!mounted) return;
final appState = Provider.of<AppState>(context, listen: false);
appState.updateSeconds(210000, 210000);
}

bool isTimerRunning(BuildContext context) {
final secondsLeft = context.watch<AppState>().secondsLeft;
if (secondsLeft == null) return false;
return secondsLeft > 0;
appState.updateTimer(appState.timer.increaseTimerDuration(duration));
}

@override
Widget build(BuildContext context) {
var title = widget.plan.days.replaceAll(",", ", ");
title = title[0].toUpperCase() + title.substring(1).toLowerCase();
final timerRunning = isTimerRunning(context);
final timerRunning = context.watch<AppState>().timer.isRunning();

return Scaffold(
appBar: AppBar(
Expand Down Expand Up @@ -255,10 +249,15 @@ class _StartPlanPageState extends State<StartPlanPage> {
tooltip: "Add 1 min",
onPressed: () {
android.invokeMethod('add');
final appState =
Provider.of<AppState>(context, listen: false);
appState.updateSeconds((appState.secondsLeft ?? 0) + 60,
(appState.secondsTotal ?? 0) + 60);
final appState = Provider.of<AppState>(
context,
listen: false,
);
appState.updateTimer(
appState.timer.increaseTimerDuration(
const Duration(minutes: 2),
),
);
},
child: const Icon(Icons.add),
),
Expand Down
31 changes: 31 additions & 0 deletions lib/timer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class Timer {
Timer(Duration totalTimerDuration, Duration elapsedTimerDuration,
DateTime timeStamp)
: _totalTimerDuration = totalTimerDuration,
_elapsedTimerDuration = elapsedTimerDuration,
_timeStamp = timeStamp;

static Timer emptyTimer() =>
Timer(Duration.zero, Duration.zero, DateTime.now());

Duration getElapsed() => _totalTimerDuration != Duration.zero
? DateTime.now().difference(_timeStamp) + _elapsedTimerDuration
: Duration.zero;

Duration getDuration() => _totalTimerDuration;

bool isRunning() => (getDuration() - getElapsed()).inMilliseconds > 0;

Timer increaseTimerDuration(Duration increase) {
if (!isRunning()) return this;
return Timer(
_totalTimerDuration + increase,
_elapsedTimerDuration,
_timeStamp,
);
}

final Duration _totalTimerDuration;
final Duration _elapsedTimerDuration;
final DateTime _timeStamp;
}
4 changes: 2 additions & 2 deletions lib/timer_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ class _TimerPageState extends State<TimerPage> {
@override
Widget build(BuildContext context) {
final appState = context.watch<AppState>();
final duration = appState.secondsTotal ?? 0;
final elapsed = (duration) - (appState.secondsLeft ?? 0);
final duration = appState.timer.getDuration().inSeconds;
final elapsed = appState.timer.getElapsed().inSeconds;

return Scaffold(
appBar: AppBar(
Expand Down

0 comments on commit 0508543

Please sign in to comment.