How to have persistent badge icon on iOS using Flutter - ios

Is it possible to use flutter to keep the notification badge on the app icon even after opening and closing the app? Ex.: user has a badge of value 6, opens app to read one message, closes app, badge now reads as 5.
How do I achieve this functionality? (Specifically looking for iOS solution, but also interested in hearing about Android side if you have tips)

One way of achieving this is using the flutter_app_badger package, which allows you to set the app badge using the updateBadgeCount function. The trick is that you need to call this function at least once when your app is in the foreground before the app is put to the background or closed. One way to do this is to extend WidgetsBindingObserver and override didChangeAppLifecycleState in one of your widgets at the top of the widget tree:
class HomeScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
switch (state) {
case AppLifecycleState.resumed:
print("app in resumed");
if (PushNotificationsManager.appBadgeSupported) {
FlutterAppBadger.updateBadgeCount(14);
}
break;
case AppLifecycleState.inactive:
print("app in inactive");
break;
case AppLifecycleState.paused:
print("app in paused");
break;
case AppLifecycleState.detached:
print("app in detached");
break;
}
}
}
The app badge will not persist by itself, which is why you need to call this function at least once each time your app is in the foreground and a great place to do that is in didChangeAppLifecycleState when AppLifecycleState changes. If you call updateBadgeCount in the AppLifecycleState.resumed state like above, you'll also need to call updateBadgeCount once when your app starts (you can do this in the init function of a class like PushNotificationsManager if you have one, otherwise just do it in one of your widgets init function).
You can also put updateBadgeCount in the other states like AppLifecycleState.inactive or AppLifecycleState.paused, which will work in most cases but be cautious about this because if the app is closed/terminated without the inactive or paused state triggering, then the app badge will not be updated because the updateBadgeCount function is not called.
For completeness: when your app is closed or in the background, you can update your app badge using Apple Push Notification service. Include the badge number in the payload, as shown here. Then when the user opens your app, the code above will execute and the badge number will be updated again so that when the user closes the app or the app goes into the background, the badge number will "persist" as seen by the user.
More about WidgetsBindingObserver and how to detect if app is in foreground/background here.

Related

listen to click-event from connected button BLE peripheral, while app is in background <react-native ble manager iOS >

As the title suggests, im trying to listen for click-event from my connected BLE peripheral device even after my react-native app is killed/background mode.
While connected i have a notification subscription on my BLE peripheral device and everytime i press button on device, my app gets notified. I want this subscription to last even if the user kills the application.
The app works fine in foreground and inactive, but when i kill the app on iOS it stops responding to button click.
On android i found a library react-native-background-actions which helped solve this. Here is the background code that is currently working on android.
import BackgroundJob from "react-native-background-actions";
playing = BackgroundJob.isRunning();
const sleep = (time) =>
new Promise((resolve) => setTimeout(() => resolve(), time));
BackgroundJob.on("expiration", () => {
console.log("iOS: I am being closed!");
});
const taskRandom = async (taskData) => {
if (Platform.OS === "ios") {
console.warn(
"This task will not keep your app alive in the background by itself, use other library like react-native-track-player that use audio,",
"geolocalization, etc. to keep your app alive in the background while you excute the JS from this library."
);
}
await new Promise(async (resolve) => {
// For loop with a delay
const { delay } = taskData;
console.log(BackgroundJob.isRunning(), delay);
for (let i = 0; BackgroundJob.isRunning(); i++) {
console.log("Ran -> ", i);
// await BackgroundJob.updateNotification({
// taskDesc: "Emergency -> " + i,
// });
await sleep(delay);
}
});
};
const options = {
taskName: "Example",
taskTitle: "ExampleTask title",
taskDesc: "ExampleTask desc",
taskIcon: {
name: "ic_launcher",
type: "mipmap",
},
color: "#ff00ff",
linkingURI: "exampleScheme://chat/jane",
parameters: {
delay: 30000,
},
};
/**
* Toggles the background task
*/
export const toggleBackground = async () => {
playing = !playing;
if (playing) {
try {
console.log("Trying to start background service");
await BackgroundJob.start(taskRandom, options);
console.log("Successful start!");
} catch (e) {
console.log("Error", e);
}
} else {
console.log("Stop background service");
await BackgroundJob.stop();
}
};
I tried reading the core bluetooth background processing for iOS apps and added a restoration identifier on my start method like this:
BleManager.start({
showAlert: true,
restoreIdentifierKey: "IDENTIFIER",
queueIdentifierKey: "IDENTIFIER",
}).then(() => {
console.log("Module initialized");
});
Does anyone have a suggestion on how to keep the subscription while app is in background? react-native-background-actions suggests audio or geolocation libraries, but these are not relevant for my application.
Any help would be greatly appreciated.
You have two very different states you are talking about; Background and killed.
If your app is not onscreen but is still in memory then it is "suspended". It remains suspended until
It is brought back into the foreground by the user
A supported background event occurs and it executes briefly while before becoming suspended again.
It is removed from memory because iOS needs resources
It is removed from memory by the user "Swiping it away"
If an iOS app is suspended and you have added Bluetooth background mode to your app then it will just work; This is scenario 2.
You need to consider what you want to do if the peripheral goes out of range; Do you want to try and reconnect or do you want to give up.
If you want to try and reconnect then you simply call connect in response to a disconnection. If the peripheral comes into range then Core Bluetooth will provide a call-back to your app (even if it is suspended). This applies in both scenario 1 & 2.
An app can be in two different "killed" states; terminated by the system and terminated by the user. These are scenarios 3 & 4
If the app is terminated by the system, scenario 3, and you have set up Core Bluetooth state restoration when you initialised Core Bluetooth then iOS will relaunch your app. When relaunched you need to set up Core Bluetooth again and then the event will be delivered to your app as usual.
If the app is terminated by the user swiping up, scenario 4, then generally speaking your app is dead until the user relaunches it.
You haven't shown how you are using Core Bluetooth, so I can't offer any concrete suggestions but I can say the approach you have shown, trying to keep your app running in the background, is not the right approach and will definitely not work on iOS. There may even be a better approach on Android, but I am not familiar with that platform. Generally keeping an app around, in memory, performing useless work is just wasting memory and battery resources.

Notification not come when app is killed in vivo

I have implemented the notification code in xamarin android. I tested this code on Samsung device using android version Pie works fine,this works fine.However when
I tested on Vivo the notification does not come up when the app is killed and android version is (Pie on both).
Chinese ROMs using (Oxygen OS, MIUI etc) when the apps are swiped from the recent app tray your app gets terminated ( similar to Force Stop). And due to this every task running in the background like Services, Jobs gets killed with the app. Even High priority FCM doesn’t see the daylight in Chinese ROMs
So user needs to enable auto start in settings for app to keep background service/Job running.By default it is off.To do this in some of the devices we can use,
try this:
public class MainActivity : AppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.activity_main);
Intent intent = new Intent();
string manufacturer = Android.OS.Build.Manufacturer;
switch (manufacturer)
{
case "xiaomi":
intent.SetComponent(new ComponentName("com.miui.securitycenter",
"com.miui.permcenter.autostart.AutoStartManagementActivity"));
break;
case "oppo":
intent.SetComponent(new ComponentName("com.coloros.safecenter",
"com.coloros.safecenter.permission.startup.StartupAppListActivity"));
break;
case "vivo":
intent.SetComponent(new ComponentName("com.vivo.permissionmanager",
"com.vivo.permissionmanager.activity.BgStartUpManagerActivity"));
break;
}
IList<ResolveInfo> arrayList = PackageManager.QueryIntentActivities(intent,
PackageInfoFlags.MatchDefaultOnly);
if (arrayList.Count> 0)
{
StartActivity(intent);
}
}
more info you could refer to Enable background services and Jobs (or FCM) in Chinese ROMs

Navigator push same screen multiple times

I need push new screen automatic on app startup (if user is login or sign out).
I am use scoped_model for auth so need navigate when user value is change in model.
I am follow Brian Egan suggestion here: https://github.com/brianegan/scoped_model/issues/43#issuecomment-442444143
class LoginScreenState extends State<LoginScreen> {
#override
void didChangeDependencies() {
ScopedModel.of<AuthModel>(context).addListener(_navigationListener);
super.didChangeDependencies();
}
#override
void dispose() {
ScopedModel.of<AuthModel>(context)
.removeListener(_navigationListener);
super.dispose();
}
void _navigationListener() {
switch (ScopedModel.of<AuthModel>(context).AuthStatus) {
case AuthStatus.NotAuth:
Navigator.of(context).pushNamed(‘/Login’);
break;
case AuthStatus.Auth:
Navigator.of(context).pushNamed(‘/Main’);
break;
case AuthStatus.Register:
Navigator.of(context).pushNamed(‘/Register’);
break;
}
AuthStatus is Enum. I change value in Model.
This is push route correct, but have issue:
Same route is push many times. For example, same Login page is push at least 5 times.
How to stop Navigator from push same screen multiple times?
Thanks!
In Brian Egan's example he had a boolean test in the _navigationListener method. His comment was:
// This function will be run every time the model changes! We will use it to
// check the navigate boolean. If it's set to true, we'll push a new screen!
//
// If not, we won't do anything.
So, a similar boolean needs to be used in your code to only navigate once despite how many times the method is called.

Flutter - Will BLoC stream instances cause memory leak when a widget is closed?

There are some scenarios where screens with their respective BLoCs are frequently created and closed. So I'm somewhat concerned about memory safety of the Streams instances created in this process, because it doesn't seem they are disposed somewhere or whether they are GC-ed. This clearly depends on the specific implementation of DART libraries and flutter. So if you know about their behavior, please let me know.
These are some scenarios I have encountered.
Multi-tab browser-like application.
Navigation through screens. (But it's not that harmful.)
showDialog() senarios when there are BLoCs inside the dialog. This is a far more common senario. There could be a lot of dialog popping up frequently in an app.
I wonder if it is necessary to override dispose() function and explicitly close all streams in BLoCProvider. It seems existing tutorials didn't mention it.
Streams will properly be cleaned as long as they aren't used anymore.
The thing is, to simply removing the variable isn't enough to unsure it's unused. It could still run in background.
You need to call Sink.close() so that it stops the associated StreamController, to ensure resources can later be freed by the GC.
To do that, you have to use StatefulWidget.dispose method:
abstract class MyBloc {
Sink foo;
Sink bar;
}
class MyWiget extends StatefulWidget {
#override
_MyWigetState createState() => _MyWigetState();
}
class _MyWigetState extends State<MyWiget> {
MyBloc bloc;
#override
void dispose() {
bloc.bar.close();
bloc.foo.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
// ...
}
}

Doing something when a widget leaves the screen

Suppose, I have a StatefulWidget, which periodically requests data from a server and this updates its state.
I prepared a Timer.periodic() to make the data get loaded off the main loop.
Now, if the widget leaves the screen, the Timer continues to call its callback.
What is the correct mount point to perform cleanup actions, when the widget get off the screen?
You have to overwrite deactivate() of the State<StatefulWidgeet>:
#override
void deactivate() {
super.deactivate();
...
}
See Flutter docs.

Resources