How to find memory leaks in Flutter? - flutter

I need to find memory leaks in Flutter.
How to find them? and how to create memory leaks for the test?

I am implemented Memory leak testing in android studio ide.
Step - 1 :
Connect your device with android studio and run your application on your device.
Step - 2 :
Go to View -> Tool Windows -> Flutter Performance
Step - 3 :
Bottom of the window Open Dev Tools option will be there, click on it. It will be navigate into new window of your browser.
See below image for more details :
Step - 4 :
To follow the steps below as per the screenshot, you can see the size and details of the object causing the memory leak.
First Select Memory from available menus than you can able to see below ui.
first: Click on settings icon
then: Mark down Dart and Flutter checkboxes.
and finally: Click on Apply button.
Step - 5 :
This is final step, now you can able to see memory leaking info.
first: Click on Snapshot it will be collect and display object list in bottom of the window.
and then: Click on search icon and Here you can see those classes which objects are not destroyed. Suppose am selected ApiRepository.dart class and instance will be available in memory ,so that details are visible in window. If multiple objects created than you can see here the total no. of instance and total size.
Step - 6 :
You can able to call Garbage Collector manually by using GC icon . You can anytime Reset and get latest snapshot using Reset and Snapshot buttons.
For more information about Memory allocation related details read below articles :
Offical document about memory leak on flutter.dev
Article about garbage collector.

You can start from reading the official documentation - https://docs.flutter.dev/development/tools/devtools/memory
The next steps describe how to run the Memory view and how to create memory leaks manually:
Press the "Open Flutter DevTools" button. It opens browser. In my case it's Safari (on Mac). If you see just a white screen, copy the link and paste it to Chrome.
Select the "Memory" tab.
Press on the graph. You will see a lot of values for the selected period of time. Take a look at "Dart/Flutter" memory usage. In my case it's 50.52 MB
You can simulate significant amount of leaks using the next code:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class MemoryLeakObject {
final String text;
MemoryLeakObject(this.text);
}
List<MemoryLeakObject> leakObjects = [];
class MemoryLeaksScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CupertinoButton(
child: const Text(
'Create 1 000 000 leaks',
),
onPressed: () {
while (leakObjects.length < 1000000) {
leakObjects.add(
MemoryLeakObject('Count: ${leakObjects.length}'),
);
}
},
),
),
);
}
}
Open the screen with the code above and press the 'Create 1 000 000 leaks' button.
Take a look at the graph again. In my case the "Dart/Flutter" memory usage increased to 101.28 MB. Also a Snapshot was created with all the objects in the memory. As you can see, there are 933365 objects of the "MemoryLeakObject" class.

If you want/need to add non-integration tests to reproduce/fix the memory leak, here is the approach. In short, it runs on your host by simply flutter test, and does not depend on a simulator/real-device at all.
Example usage: https://gist.github.com/fzyzcjy/e68c375643d7c77942cdc8fb5f01de18
Code (without example):
import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'dart:isolate';
import 'package:common_dart/utils/processes.dart';
import 'package:front_log/front_log.dart';
import 'package:test/test.dart';
import 'package:vm_service/vm_service.dart' hide Isolate, Log;
import 'package:vm_service/vm_service.dart' as vm_service;
import 'package:vm_service/vm_service_io.dart';
const _kTag = 'vm_services';
// #4657
FutureOr<void> runTestsInVmService(
FutureOr<void> Function(VmServiceUtil) body, {
required String selfFilePath,
}) async {
Log.d(_kTag, 'runInVmService selfFilePath=$selfFilePath Platform.script.path=${Platform.script.path}');
if (Platform.script.path == selfFilePath) {
final vmService = await VmServiceUtil.create();
tearDownAll(vmService.dispose);
await body(vmService);
} else {
test(
'run all tests in subprocess',
// #4764
timeout: const Timeout(Duration(seconds: 60)),
() async {
await executeProcess('dart', ['run', '--enable-vm-service', selfFilePath]);
},
);
}
}
/// https://stackoverflow.com/questions/63730179/can-we-force-the-dart-garbage-collector
class VmServiceUtil {
static const _kTag = 'VmServiceUtil';
final VmService vmService;
VmServiceUtil._(this.vmService);
static Future<VmServiceUtil> create() async {
final serverUri = (await Service.getInfo()).serverUri;
if (serverUri == null) {
throw Exception('Cannot find serverUri for VmService. '
'Ensure you run like `dart run --enable-vm-service path/to/your/file.dart`');
}
final vmService = await vmServiceConnectUri(_toWebSocket(serverUri), log: _Log());
return VmServiceUtil._(vmService);
}
void dispose() {
vmService.dispose();
}
Future<void> gc() async {
final isolateId = Service.getIsolateID(Isolate.current)!;
final profile = await vmService.getAllocationProfile(isolateId, gc: true);
Log.d(_kTag, 'gc triggered (heapUsage=${profile.memoryUsage?.heapUsage})');
}
}
String _toWebSocket(Uri uri) {
final pathSegments = [...uri.pathSegments.where((s) => s.isNotEmpty), 'ws'];
return uri.replace(scheme: 'ws', pathSegments: pathSegments).toString();
}
class _Log extends vm_service.Log {
#override
void warning(String message) => Log.w(_kTag, message);
#override
void severe(String message) => Log.e(_kTag, message);
}

Related

GetX Unbind Stream

I am using the bindStream() function with the GetX package inside a controller.
class FrediUserController extends GetxController {
#override
void onReady() {
super.onReady();
final userController = Get.find<FrediUserController>();
var groupIds = userController.user.groups;
groupList.bindStream(DatabaseManager().groupsStream(groupIds));
ever(groupList, everCallback);
}
}
But, when the groupIds update in the FrediUserController (with an ever function that gets triggered, I want to RE-bind the streams. Meaning, delete the existing ones and bind again with new ids, or replace the ones that have changed.
Temporary Solution: Inside ever() function
Get.delete<FrediGroupController>();
Get.put(FrediGroupController());
This code gets run everytime my groupIds change from the database. But I do not want to initiate my controllers every time a small thing changes, it is bad UX.
This seems difficult, could someone guide me to the right direction? Maybe there is a completely different approach to connecting two GetX controllers?
Note: the first one include editing the source code of the Getx package.
first:
looking in the source code of the package :
void bindStream(Stream<T> stream) {
final listSubscriptions =
_subscriptions[subject] ??= <StreamSubscription>[];
listSubscriptions.add(stream.listen((va) => value = va));
}
here is what the bind stream actually do, so if we want to access the listSubscriptions list, I would do:
final listSubscriptions;
void bindStream(Stream<T> stream) {
listSubscriptions =
_subscriptions[subject] ??= <StreamSubscription>[];
listSubscriptions.add(stream.listen((va) => value = va));
}
now from your controller you will be able to cancel the streamSubscription stored in that list with the cancel method like this :
listSubscriptions[hereIndexOfThatSubscription].cancel();
then you can re-register it again with another bindStream call
second :
I believe also I've seen a method called close() for the Rx<T> that close the subscriptions put on it, but I don't know if it will help or not
Rx<String> text = ''.obs;
text.close();
I've also run into this issue, and there appears to be no exposed close function. There is a different way to do it though, using rxdart:
import 'package:get/get.dart';
import 'package:rxdart/rxdart.dart' hide Rx;
class YourController extends GetxController {
final value = 0.obs;
final _closed = false.obs;
void bindValue(Stream<int> valueStream) {
_closed
..value = true
..value = false;
final hasClosed = _closed.stream.where((c) => c).take(1);
value.bindStream(
valueStream.takeUntil(hasClosed)
);
}
}
Whenever you want to unbind, just set _closed.value = true.

Flutter: how can I permanently register a sensor (and never unregister it?)

TL;DR how can I have an Android sensor permanently running/active/registered for my app, even if I close it?
Objective:
I'm making a Flutter application that counts your steps using the pedometer package,
which uses the built-in sensor TYPE_STEP_COUNTER of Android,
which returns the # of steps taken since last boot (iOS). On Android, any steps taken before installing the app are not counted.
How I implemented it:
When the app is actively running in the foreground, each step causes
a myStepCount to increment by 1.
In all other cases (phone locked, went to home-screen, closed the app...), the android TYPE_STEP_COUNTER sensor should
still be running in the background, and once I open my app again, the
difference between new stepCount and last saved stepCount (saved
using shared_prefs) will be calculated and added to myStepCount.
Important:
The TYPE_STEP_COUNTER sensor must be permanently running/stay registered in the background, even after I lock my phone, go to the home-screen, or close the app...
Observations:
On my Samsung Galaxy A02s, my app works perfectly fine, as it it supposed to
(as described above). That is because on that phone I also have the
Google Fit app installed, which tracks your steps 24/7 (so the
TYPE_STEP_COUNTER sensor is permanently registered).
On my Samsung Galaxy S7, my app does not work as it's supposed to.
myStepCount gets incremented when I take steps while the app is
running in the foreground. But steps taken while the app is closed
will NOT be added to myStepCount once I open the app again.
Note: I don't have any other step-counting-apps like Google Fit on this phone.
Conclusion:
I need to find a way to register the TYPE_STEP_COUNTER sensor from my Flutter app, and keep it registered even after I close the app.
2 Attempted (but unsuccessful) Solutions:
1st Attempt:
Calling Native Android Code from my Flutter Code to register the sensor
This is my main.dart file (with the unimportant parts left out for simplicity):
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(App());
}
class App extends StatefulWidget {
#override
_AppState createState() => _AppState();
}
class _AppState extends State<App> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
if (Platform.isAndroid) {
_activateStepCounterSensor();
} else if (Platform.isIOS) {
//TODO check if anything is needed to to here
}
}
void _activateStepCounterSensor() async {
MethodChannel _stepCounterChannel = MethodChannel('com.cedricds.wanderapp/stepCounter'); //convention
dynamic result = await _stepCounterChannel.invokeMethod('activateStepCounterSensor');
switch (result) {
case "success":
//The following line gets printed when I run the flutter app on my Samsung Galaxy S7:
print('_activateStepCounterSensor(): successfully registered step counter sensor for android');
break;
case "error":
print('_activateStepCounterSensor(): failed to register step counter sensor (not available) for android');
//TODO display errorpage (because app is completely useless in this case)
break;
default:
print('_activateStepCounterSensor(): unknown result: $result');
break;
}
}
//build() and other lifecycle-methods and helper methods: not important for this question
}
This is my MainActivity.kt file:
package com.cedricds.wanderapp
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.util.Log
import android.widget.Toast
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity2: FlutterActivity(), SensorEventListener {
private val STEP_COUNTER_CHANNEL = "com.cedricds.wanderapp/stepCounter";
private lateinit var channel: MethodChannel
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, STEP_COUNTER_CHANNEL)
channel.setMethodCallHandler { call, result ->
when(call.method){ //this is like switch-case statement in Java
"activateStepCounterSensor" -> {
activateStepCounterSensor(result)
}
}
}
}
private var sensorManager : SensorManager?=null
private var sensor: Sensor ?= null
private fun activateStepCounterSensor(result: MethodChannel.Result) {
//This line gets printed when I run the flutter app, so the method gets called successfully:
Log.d("Android", "Native Android: activateStepCounterSensor()")
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager?.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
if (sensor == null) {
Toast.makeText(this, "missing hardware.", Toast.LENGTH_LONG).show()
result.error("error", "error", "error")
} else {
sensorManager?.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL)
//This line gets printed:
Log.d("Android", "Native Android: registered TYPE_STEP_COUNTER")
//and never unregister that listener
result.success("success")
}
}
override fun onSensorChanged(p0: SensorEvent?) {}
override fun onAccuracyChanged(p0: Sensor?, p1: Int) {}
}
Despite the few print(...) and Log.d(...) being printed in the console as expected, the app doesn't work how I expected it to work. When I exit the app, walk for example 50 steps, then open the app again, those 50 steps are missing. It seems the sensor is being unregistered somewhere.
2nd Attempt:
Modifying the pedometer package's code by removing unregisterListener(...):
The only changes I did to the file were 2 Log.d(...) statements and more importantly, commenting out a specific line of code.
modified SensorStreamHandler.kt from the pedometer package:
package com.example.pedometer
import android.content.Context
import android.hardware.Sensor
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.os.Looper
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import android.os.Handler
import android.util.Log
class SensorStreamHandler() : EventChannel.StreamHandler {
private var sensorEventListener: SensorEventListener? = null
private var sensorManager: SensorManager? = null
private var sensor: Sensor? = null
private lateinit var context: Context
private lateinit var sensorName: String
private lateinit var flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
constructor(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding, sensorType: Int) : this() {
this.context = flutterPluginBinding.applicationContext
this.sensorName = if (sensorType == Sensor.TYPE_STEP_COUNTER) "StepCount" else "StepDetection"
sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager!!.getDefaultSensor(sensorType)
this.flutterPluginBinding = flutterPluginBinding
}
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
Log.d("Pedometer", "Native Android: onListen()")
if (sensor == null) {
events!!.error("1", "$sensorName not available",
"$sensorName is not available on this device");
} else {
sensorEventListener = sensorEventListener(events!!);
sensorManager!!.registerListener(sensorEventListener, sensor, SensorManager.SENSOR_DELAY_NORMAL);
}
}
override fun onCancel(arguments: Any?) {
Log.d("Pedometer", "Native Android: onCancel()")
//The only change I did: commenting out the following line:
// sensorManager!!.unregisterListener(sensorEventListener);
}
}
This also did not solve my problem. So if someone knows how I can permanently register the TYPE_STEP_COUNTER sensor in my flutter app, please let me know.
Update:
I've contacted one of the developers of the pedometer package, and he suggested me to use flutter_foreground_service (which is developed by the same team/company as pedometer). It works.
But I would still find it interesting, if there is another way (maybe similar to my 2 failed attempts).

how to show my screen alarm from background manager

I installed android alarm manager package and print code on background its work well.
But how can show my alarm screen like WhatsApp receive call for example,
can I do this with flutter ?
void runOnBackGround() async {
final int helloAlarmID = 0;
await AndroidAlarmManager.initialize();
await AndroidAlarmManager.periodic(
const Duration(seconds: 1), helloAlarmID, callBack,
wakeup: true);
}
void callBack(i) async {
final DateTime now = DateTime.now();
print("[$now] id = $i Hello, world! ");
}
Well in my case, I modified the plugin code and needed some permission. I found this solution from geisterfurz007/random-alarm. It makes can show the app from the background.
1. Modifying plugin
EDIT: To open an app when an alarm goes off, Need to modify the plugin.
Open the project and find the android_alarm_manager-2.0.0 in the External Libraries/Flutter Plugins. And find AlarmBroadcastReceiver.java, copy-paste the following code. That code is from the flutter issue.
AlarmBroadcastReceiver.java
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.plugins.androidalarmmanager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import android.content.pm.PackageManager;
import android.view.WindowManager;
public class AlarmBroadcastReceiver extends BroadcastReceiver {
private static PowerManager.WakeLock wakeLock;
#Override
public void onReceive(Context context, Intent intent) {
PowerManager powerManager = (PowerManager)
context.getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
PowerManager.ACQUIRE_CAUSES_WAKEUP |
PowerManager.ON_AFTER_RELEASE, "My wakelock");
Intent startIntent = context
.getPackageManager()
.getLaunchIntentForPackage(context.getPackageName());
startIntent.setFlags(
Intent.FLAG_ACTIVITY_REORDER_TO_FRONT |
Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
);
wakeLock.acquire();
context.startActivity(startIntent);
AlarmService.enqueueAlarmProcessing(context, intent);
wakeLock.release();
}
}
2. Permissions
There is a need for permission to run the app when the alarm goes off from the background.
Display over other apps
Ignore battery optimization
You can access permissions by using permission_handler.

Why isn't Gtk Filechooser Button selecting any file in my flatpak build?

I have a file chooser button that triggers a change in the titlebar whenever a file is selected with it. And it seems to work fine in my non-flatpak build.
import gtk.Application : Application;
import gtk.ApplicationWindow : ApplicationWindow;
import gio.Application : GioApp = Application;
import gtkc.gtktypes : GApplicationFlags, FileChooserAction;
import gtk.FileChooserButton : FileChooserButton;
const string AppID = `org.github.flatfcbtest`;
int main(string[] args)
{
auto app = new App();
return app.run(args);
}
public class App : Application
{
public:
this(const string appID = AppID, const GApplicationFlags flags = GApplicationFlags.FLAGS_NONE)
{
super(appID, flags);
addOnActivate(delegate void(GioApp _) {
auto pw = new PrimaryWindow(this);
pw.showAll();
});
}
}
class PrimaryWindow : ApplicationWindow
{
this(Application app)
{
super(app);
setSizeRequest(500, 300);
auto fcb = new FileChooserButton(`Select file`, FileChooserAction.OPEN);
fcb.addOnFileSet(delegate void (FileChooserButton _) {
setTitle(`file set!`);
});
add(fcb);
}
}
(GtkD reference)
However in my flatpak builds, the file selected with the chooser button does not select anything and it keeps saying (None). However my titlebar is changes accordingly so I know that the signal was emitted by the file chooser button.
Here is my flatpak permissions list:
finish-args:
- --socket=fallback-x11
- --share=ipc
- --filesystem=host
- --device=all
- --socket=session-bus
What's causing this?
Typically if you're shipping a flatpak, you want to avoid --filesystem=host and just use GtkFileChooserNative instead. This class supports portals, allowing a user to select files the application does not have permission to access by itself.
This is a much better approach than giving the application full filesystem access. GtkFileChooserNative will still work in a non-flatpak application and you shouldn't notice any difference unless you're doing something fancy.
As for your question of why GtkFileChooser is not working with --filesystem=host however, I do not know.

How to delete all the boxes in the hive in flutter?

I am using Hive to store the data locally, but the boxes are created dynamically throughout the apps and don't know how many boxes are there in total.
I want to delete all the boxes, whether open or closed, when the user presses the reset button.
So far, I could delete all open boxes or the particular box but not all.
Is there is a way to do that? Or is there any way to open all the boxes at once?
If you want to close all open boxes
Hive.close();
If you want to delete all currently open boxes from disk
Hive.deleteFromDisk();
I created this extension:
import 'dart:async';
import 'dart:io';
import 'package:hive/hive.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
extension on HiveInterface {
/// Get a name list of existing boxes
FutureOr<List<String>> getNamesOfBoxes() async {
final appDir = await getApplicationDocumentsDirectory();
var files = appDir.listSync();
var _list = <String>[];
files.forEach((file) {
if (file.statSync().type == FileSystemEntityType.file
&& p.extension(file.path).toLowerCase() == '.hive') {
_list.add(p.basenameWithoutExtension(file.path));
}
});
print('Current boxes: $_list');
return _list;
}
/* ---------------------------------------------------------------------------- */
/// Delete existing boxes from disk
void deleteBoxes() async {
final _boxes = await this.getNamesOfBoxes();
if (_boxes.isNotEmpty) _boxes.forEach((name) => this.deleteBoxFromDisk(name));
}
}
As of now, I have to keep track of the boxes by again creating the box to store all the box information, and delete the box by reading the number of boxes stored and then resetting the box info also.