I am trying to get a handle on the nearly entirely undocumented Android Alarm Manager Plus, and have a very simple app to press a button, set an alarm, and fire the alarm as follows:
import 'package:flutter/material.dart';
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await AndroidAlarmManager.initialize();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.pink,
),
home: SetAlarmPage(),
);
}
}
class SetAlarmPage extends StatefulWidget {
const SetAlarmPage({Key? key}) : super(key: key);
#override
State<SetAlarmPage> createState() => _SetAlarmPageState();
}
class _SetAlarmPageState extends State<SetAlarmPage> {
String test = "Press Me!";
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Set an Alarm")),
body: Center(
child: ElevatedButton(
child: Text(test),
onPressed: () {
print(test + " Button Pressed...");
setAlarm();
},
),
),
floatingActionButton:
FloatingActionButton(onPressed: null, child: Icon(Icons.add)),
);
}
void setAlarm() async {
print("setAlarm");
final int alarmID = 1;
await AndroidAlarmManager.oneShot(Duration(minutes: 1), alarmID, playAlarm);
}
void playAlarm() {
print("playAlarm");
setState(() {
test = "Pressed!";
});
}
}
I manage to get the alarm service started, but beyond that, nothing. I have tried initializing the AndroidAlarmManager object both in main and in setAlarm, tried moving around ensureInitialized, tried setting different durations in oneShot, tried changing the ID, and tried firing a more simple alarm function. No matter what I do, the alarm wont set or fire.
I'm pretty sure its something simple, but for a core function of android, there is no real documentation on how to use it to speak of.
Does anyone know what android alarm manager plus wants that I'm not providing, here?
first did you add the required AndroidManifest.xml tags?
second thing, by reading the documentation on https://pub.dev/packages/android_alarm_manager_plus, the callback is executed on a separate Isolate thus you can't pass a function from an instance class since isolates don't share memory (isolate is to run a piece of code on another thread).
You can make sure that the plugin is working by adding a static function with a print statement (you can't call setState from a static function)
change the playAlarm function into:
static void playAlarm() {
print("playAlarm");
}
this function is used to verify that the plugin is working
Related
I'm developing a flutter app using Bloc, Division, and Get for the navigation.
I have a text field reusable component that I use in most of the fields that I create, the text field works properly on iOS without any issues, but on Android the Keyboard immediately disappear when I tap the field.
Here are the things that I've done but it still doesn't work:
Not using text field from the component, but directly create another one on the page
Create my own testing page that only used to test the text field
Using resizeToAvoidBottomInset: false, defined the controller as a static final
The only thing that works is when I create another flutter project from the start.
my-testing.dart
import 'package:flutter/material.dart';
class MyTesting extends StatefulWidget {
const MyTesting({Key? key}) : super(key: key);
#override
State<MyTesting> createState() => _MyTestingState();
}
class _MyTestingState extends State<MyTesting> {
late TextEditingController controller;
#override
void initState() {
controller = TextEditingController();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Just testing'),
),
body: Padding(
padding: const EdgeInsets.all(20),
child: TextField(
controller: controller,
),
),
);
}
}
I am trying to get notified, whenever the user changes the theme of the operating system. I want to use Provider to accomplish that, however dart Provider needs a Stream that gives a Snapshot, whenerver somthing is changed or getsupdated. So I need to emplement or rather use a Stream, that gives me a snapshot whenever the os theme gets changed.
Here is my code. It is nothing special. But I really want to know how to get this Provider up and running with a Stream
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MaterialApp(initialRoute: '/', routes: {
'/': (context) => MainPage(),
}));
This class is a wrapper for the HomePage. It contains the Provider.
(value: brightnessStream) is a dummy value, and that is what I need to implement.
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
#override
Widget build(BuildContext context) {
return StreamProvider<Brightness>.value(
initialData: Brightness.light,
value: brightnessStream,
child: Home(),
);
}
}
In this class I am listening to the Stream, whenever the brightness changes and displying a text that shows the current theme.
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
final brightness = Provider.of<Brightness>(context);
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text('App'),
),
body: Center(
child: Text(brightness.toString()),
),
);
}
}
the stream should like somethig like this.
Stream<Brightness> get brightnessStream {
// return stream of os brigtness (os theme)
}
So how is it possible?
Here's how you can set different colors for light and dark mode, the app will automatically switch if the phone is set to dark mode or light mode.
MaterialApp(
theme: ThemeData(
brightness: Brightness.light,
primaryColor: Colors.red,
),
darkTheme: ThemeData(
brightness: Brightness.dark,
// additional settings go here
),
);
You can also get the platform brightness (Brightness.light / Brightness.dark) using
WidgetsBinding.instance.window.platformBrightness
but you will have to use the WidgetsBindingObserver mixin and override the method below
#override
void didChangePlatformBrightness() {
print(WidgetsBinding.instance.window.platformBrightness); // should print Brightness.light / Brightness.dark when you switch
super.didChangePlatformBrightness(); // make sure you call this
}
and then inside the didChangePlatformBrightness you can add to your stream.
This is also duplicate.
click here to view
Thank you for your answers. I solved the Problem like this:
class Theme {
final window = WidgetsBinding.instance.window;
final _controller = StreamController<Brightness>();
Theme() {
window.onPlatformBrightnessChanged = () {
// This callback gets invoked every time brightness changes
final brightness = window.platformBrightness;
_controller.sink.add(brightness);
};
}
Stream<Brightness> get stream => _controller.stream;
}
so I built my own stream
I have an issue with flutter. I have managed to implement a basic navigation system that keeps state when you do either of the following:
switch between tabs
press the android home button and re-open the app (either by clicking on the app again or using the list of active app button (the little square at the bottom))
But if I press the back button - going back to the android homescreen I completely lose state. I have re-implemented some code to randomly generate a number and display it on the app - this way I know if I'm getting the same widget or a new one has been built.
Why do I need this? (if you're interested)
I'm creating an audio app and when I click play song, it plays. but when I click back to the home screen and let it play in the background -> then open the app again, I can play it again and have it playing twice!
Main:
import 'package:flutter/material.dart';
import 'BottomNavigationBarController.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Login',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BottomNavigationBarController(),
);
}
}
Bottom navigation tab (BottomNavigationBarController):
import 'package:flutter/material.dart';
import 'PlaceholderWidget.dart';
class BottomNavigationBarController extends StatefulWidget {
BottomNavigationBarController({Key key}) : super(key: key);
#override
_BottomNavigationBarController createState() => _BottomNavigationBarController();
}
class _BottomNavigationBarController extends State<BottomNavigationBarController>{
int _selectedPage = 0;
List<Widget> pageList = List<Widget>();
#override
void initState() {
pageList.add(PlaceholderWidget());
pageList.add(PlaceholderWidget());
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _selectedPage,
children: pageList,
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.phone_android),
title: Text('First Page'),
),
BottomNavigationBarItem(
icon: Icon(Icons.phone_android),
title: Text('Second Page'),
),
],
currentIndex: _selectedPage,
selectedItemColor: Colors.blue,
onTap: _onItemTapped,
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
void _onItemTapped(int index) {
setState(() {
_selectedPage = index;
});
}
}
Random number widget (PlaceholderWidget):
import 'dart:math';
import 'package:flutter/material.dart';
class PlaceholderWidget extends StatefulWidget {
PlaceholderWidget({Key key, this.color}) : super(key: key);
final Color color;
#override
_PlaceholderWidget createState() => _PlaceholderWidget();
}
class _PlaceholderWidget extends State<PlaceholderWidget> with AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
return Container(
color: widget.color,
child: Text(random_num().toString()),
);
}
int random_num(){
Random random = new Random();
int randomNumber = random.nextInt(100);
return randomNumber;
}
}
Any help will be appreciated :)
I think Navigator pop deletes the widget not entirely sure. But If you want to save the current state just use navigator push don't pop. Also use named routes this will help you greatly.
Use Provider to pass state and keep a global store.
If your app will need to scale, now is a good time to start with MobX/BLoC/Redux/InheritedWidget.. etc.
I'm attempting to run location updates on a Flutter isolate thread, the error is only present when running an isolate. Location requests works without issues on the main thread. The goal here is to run this as a background service, working with dart code only.
I am using Geolocator plugin for location requests.
This is the error I am facing when starting the isolate:
Exception has occurred. FlutterError
(ServicesBinding.defaultBinaryMessenger was accessed before the
binding was initialized.
I have tried to include the WidgetsFlutterBinding.ensureInitialized() before runApp but without results.
Looking at the call stack of the error, it seems problems occur at the android location platform call: checkPermissionStatus
This happens regardless of what location plugin I am using, it stops at the permission status check.
I have figured it could have something to do with awaiting location permission user input, but this check will fail on a non-ui thread?
See this simple main.dart file for an example:
import 'dart:isolate';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Isolate location test',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Isolate location test'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Isolate isolate;
bool isRunning = false;
String output = '';
ReceivePort receivePort;
void start() async {
receivePort = ReceivePort();
await Isolate.spawn(locationUpdate, receivePort.sendPort);
receivePort.listen((dynamic data) {
setState(() {
isRunning = true;
});
}, onDone: () {
print("done");
});
}
void stop() {
if (isolate != null) {
setState(() {
isRunning = false;
});
receivePort.close();
isolate.kill(priority: Isolate.immediate);
isolate = null;
}
}
static void locationUpdate(SendPort sendPort) async {
Geolocator().checkGeolocationPermissionStatus().then((status) {
sendPort.send(status);
});
// Geolocator().getCurrentPosition().then((pos) {
// sendPort.send(pos);
// });
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(child: Text(output)),
floatingActionButton: FloatingActionButton(
onPressed: isRunning ? stop : start,
child: Icon(isRunning ? Icons.stop : Icons.play_circle_filled),
),
);
}
}
Code I show you is the simplified code which I'm troubled in.
My expected result is [1,2,3,4,5,6], but app says [1,2,3].
I know "loadMoreInterger()" should be in "initState()", but for some reason I have to put it in Widget build() {"HERE"}.
I wonder if why doesn't it work, and the solution for correct result.....
I really appreciate for your help :)
import 'package:flutter/material.dart';
import 'dart:async';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
// ↓↓↓↓↓↓↓↓↓↓↓WHERE I CANNOT UNDERSTAND↓↓↓↓↓↓↓↓↓↓↓
class _MyHomePageState extends State<MyHomePage> {
List<int> intList = [1,2,3];
Future<List<int>> loadMoreInteger() async {
print('Future');
return [4,5,6];
}
#override
Widget build(BuildContext context) {
loadMoreInteger().then((value) {
intList.addAll(value); // why doesn't it work?
});
print("console: $intList");
return Scaffold(
body: Center(
child: Text("display: $intList")
)
);
}
}
//Expected result: [1,2,3,4,5,6]
//Actual result: [1,2,3]
put it in initState override function and it works for yu !!!!
List<int> intList = new List();
Future<List<int>> loadMoreInteger() async {
print('Future');
return [4,5,6];
}
#override
void initState() {
super.initState();
intList = [1,2,3];
loadMoreInteger().then((v){
setState(() {
intList.addAll(v) ;
});
}); }
Here is what your build method does: after entering the method it starts to execute loadMoreInteger() future. Afterwards even if executed future is synchronous it only schedules call of next future that is produced by calling .then. So build method continues to execute with old intList value. And [4,5,6] will be added only after build completes.
In general you can wait for future to complete by calling it with await keyword. But build method is overriden and already has predefined return type that is not future, so you can not call await inside build.
What you can do:
I highly recommend moving any manipulation with data from build method. Its purpose is to produce widgets as fast as possible. It can be called multiple times at some moment unexpected for developer.
One of possible options for you will be moving loadMoreInteger() to initState and calling setState when intList is updated
#override
void initState() {
super.initState();
loadMoreInteger().then((value) {
setState(() {
intList.addAll(value);
});
});
}