I have searched the Flutter documentation and googled this, but with zero result. I am developing my first Flutter app for android and I would like to create a custom quick settings tile for it. I am targeting Nougat and above. I know it's possible in Java and Kotlin (e.g. https://android.jlelse.eu/develop-a-custom-tile-with-quick-settings-tile-api-74073e849457), but how about Dart/Flutter?
You can do it natively (makes more sense since it is an Android-only feature).
Every Flutter contains an android and ios folder. Inside of those folders, you will find the wrapper apps for Android and iOS.
Just open the Android project in Android Studio and follow the tutorial you linked.
You can create Quick setting tile natively
Go to android/app/src/main/kotlin/package_name(folder)
Create a service class MyTileService.kt
MyTileService.kt
#RequiresApi(Build.VERSION_CODES.N)
//Quick Tile feature can be used Build.VERSION_CODES.N or Greater
class MyTileService : TileService() {
// Called when the user adds your tile.
override fun onTileAdded() {
super.onTileAdded()
}
// Called when your app can update your tile.
override fun onStartListening() {
super.onStartListening()
val tile = qsTile // this is getQsTile() method form java, used in Kotlin as a property
tile.label = "Set Alarm"
tile.state = Tile.STATE_ACTIVE
tile.icon = Icon.createWithResource(this, R.drawable.baseline_alarm_24)
tile.updateTile() // you need to call this method to apply changes
}
// Called when your app can no longer update your tile.
override fun onStopListening() {
super.onStopListening()
}
// Called when the user taps on your tile in an active or inactive state.
override fun onClick() {
super.onClick()
try{
// to open flutter activity
val newIntent= FlutterActivity.withNewEngine().dartEntrypointArgs(listOf("launchFromQuickTile")).build(this)
newIntent.flags= Intent.FLAG_ACTIVITY_NEW_TASK
startActivityAndCollapse(newIntent)
}
catch (e:Exception){
Log.d("debug","Exception ${e.toString()}")
}
}
// Called when the user removes your tile.
override fun onTileRemoved() {
super.onTileRemoved()
}
}
In AndroidManifest.xml
<service
android:name=".MyTileService"
android:exported="true"
android:icon="#drawable/baseline_alarm_24" //your_vector_icon
android:label="Set Alarm" //tile lable
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action
android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
running your app will create a quick setting tile for your app
if not showing -> try to edit the notification panel icons (xiaomi/mi devices)
onClick on this quick settings icon we want to show our flutter screen
Inside main.dart
Future<void> main(List<String> arguments) async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp(
msg: arguments.isNotEmpty ? arguments[0] : null,
));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key, this.msg}) : super(key: key);
final String? msg;
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: msg == "launchFromQuickTile" ? Create.routeName :
Home.routeName,
routes: //your routes,
);
}
}
For more information about quick settings tile
Related
I have followed the development from this github repository but I have added only tap on mobile feature with real android device that have NFC support.
Terminal SDK verion used is:
com.stripe:stripeterminal:2.15.0
com.stripe:stripeterminal-localmobile:2.15.0
In my app I have a MainActivity and 2 fragments ( say Fragment1 and Fragment2).
Fragment 1 : Selection of amount from a set of values nothing else added here (MainActivity first load this fragment)
Fragment 2 : Show selected amount and Connecting reader and Processing Payment is added in this fragment
Issues faced on my Fragment 2 is added with Processing payment is added below.
val config = ConnectionConfiguration.LocalMobileConnectionConfiguration("xxxxxxxxxxxxxxx");
Terminal.getInstance().connectLocalMobileReader(
firstReader,
config,
object : ReaderCallback {
override fun onSuccess(reader: Reader) {
Log.d("Custom Log", "Connected to mobile device");
val params = PaymentIntentParameters.Builder()
.setAmount(30)
.setCurrency("gbp")
.build();
Terminal.getInstance().createPaymentIntent(params, object: PaymentIntentCallback {
override fun onSuccess(paymentIntent: PaymentIntent) {
// Placeholder for collecting a payment method with paymentIntent
val collectConfig = CollectConfiguration.Builder()
.updatePaymentIntent(true)
.build();
val cancelable = Terminal.getInstance().collectPaymentMethod(paymentIntent,
object : PaymentIntentCallback {
override fun onSuccess(paymentIntent: PaymentIntent) {
val pm = paymentIntent.paymentMethod
val card = pm?.cardPresentDetails ?: pm?.interacPresentDetails
// Placeholder for business logic on card before processing paymentIntent
Terminal.getInstance().processPayment(paymentIntent, object : PaymentIntentCallback {
override fun onSuccess(paymentIntent: PaymentIntent) {
// Placeholder for notifying your backend to capture paymentIntent.id
Log.d("Custom Log","Process Payment : "+paymentIntent.id);
}
override fun onFailure(exception: TerminalException) {
// Placeholder for handling the exception
Log.d("Custom Log","Error errorMessage : " + exception.errorMessage);
}
})
}
override fun onFailure(exception: TerminalException) {
// Placeholder for handling exception
}
})
}
override fun onFailure(exception: TerminalException) {
Log.d("Custom Log","Error ReaderCallback : " + exception.errorMessage)
}
})
}
override fun onFailure(e: TerminalException) {
Log.d("Custom Log","Error ReaderCallback : " + e.errorMessage)
}
});
In the above code, collectPaymentMethod brings a white screen for tap behind, I think this is a new system activity to handle NFC card reading.I have few questions here regarding customisation
q1: Is it possible to customize this screen view ?
q2: On tapping on this screen with a live card it show success message with green screen, even though the card is invalid for test payment, How can I customise this in error case
q3: Screen automatically closes and goes to MainActivity : Fragment1 , How can I catch the the closing and move to fragment2 insted of fragment1. I could'nt find a callback function to handle such cases
My tapto screen and success case screen is attached below
In processPayment of the above code, it fall into onFailure with the error:
Your card was declined. Your request was in test mode, but used a non test (live) card. For a list of valid test cards
How can I handle this error with a custom message or info in app.
As a summary, i need to do some customisation with the white screen of the above screenshot, but i couldnt find any documentations or example of that. Someone please help me to sort out these cases
Thanks in advance
I am trying to open FlutterActivity in my existing android application. Before I was creating new flutter engine every time I was opening activity like this:
FlutterActivity
.withNewEngine()
.build(context)
And everything was working fine besides a little lag while opening the activity. To get rid of the lag I wanted to switch to using cached engine. I followed this official tutorial: LINK
And ended up with smething like this:
In my Application class:
class App : Application() {
lateinit var flutterEngine: FlutterEngine
override fun onCreate() {
...
flutterEngine = FlutterEngine(this)
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
FlutterEngineCache
.getInstance()
.put("myEngineId", flutterEngine)
}
}
And later in my application on the button click, in the same place that I was successfully opening FlutterActivity:
FlutterActivity
.withCachedEngine("myEngineId")
.build(context)
So I basically followed the all the instructions but the effect that I get now is after the button click there is even longer lag than before and then there is only black screen being displayed. My flutter screen is not displayed and application is kind of frozen I can't go back or do anything. There is also no error or any useful info in the logs. I have no idea what is going on. What am I doing wrong?
To Use cached FlutterEngine
In FlutterActivity you must declare provideFlutterEngine method.
class DemoActivity : FlutterActivity() {
override fun provideFlutterEngine(context: Context): FlutterEngine? =
FlutterEngineCache.getInstance().get(FlutterConstants.ENGINE_ID)
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "demo-channel")
.setMethodCallHandler { call, result ->
if (call.method == "demo-method") {
demoMethod()
result.success(null)
} else {
result.notImplemented()
}
}
}
private fun demoMethod() {
// Do native code
}
}
How do you trigger a native Android back button press from inside a React Native button that is handled somewhere else in the application.
For example,
<Pressable onPress={nativeGoBack} />
The event is then handled in another component using the BackHandler API.
BackHandler.addEventListener('hardwareBackPress', () => {
// Perform action
});
NB: This is not a React Navigation specific question (So navigation.goBack() is not a solution).
If you want to communicate from react native to adnroid, you should use Native modules.
See documentation https://reactnative.dev/docs/native-modules-android
In short:
Create a native module which handle backbutton from specific activity
class BackPressReactModule(reactContext: ReactApplicationContext?) : ReactContextBaseJavaModule(reactContext) {
override fun getName(): String {
return "BackPressReactModule"
}
#ReactMethod
fun goBack() {
val context = reactApplicationContext
val currentActivity = context.currentActivity as Activity
currentActivity.onBackPressed()
}
}
Register BackPressReactModule into your packages in ReactNativeHost. (See documentation)
After successfully exposing module, you can call it from javascript like this:
import {NativeModules} from 'react-native';
const {BackPressReactModule} = NativeModules;
BackPressReactModule.goBack();
I need my Flutter app to be associated with the file type .foo. So that if a user opens their local file manager for example, and clicks on the file bar.foo android prompts them to open the file with my flutter app.
So far I understand, that this is basically an incoming Android intent which has to be registered with a so called intent-filter as described here: Creating app which opens a custom file extension.
But further, I do not understand how to handle it in Kotlin.
Therefore the next logical thing would be to find out how incoming intents work in Flutter. The documentation however doesn't help me, as it is explained only in Java and not Kotlin. I have no experience with Java at all and would like to stick to Kotlin anyway, because I have other platform specific code written in Kotlin.
In this post Deep Shah seems to have the same problem, but doesn't share the solution:
Support custom file extension in a flutter app ( Open file with extension .abc in flutter ).
This post by Shanks died quietly: Open custom file extension with Flutter App.
I hope to find answers or pointers to relevant resources.
I found a solution for android. This post should bundle all important steps into one. The things starting with YOUR_ are dependant on you. Change them accordingly.
First, you have to register the intent-filter in the AndroidManifest.xml. This will tell android to list your app as a possible option.
<!-- TODO: CHANGE ICON AND LABEL HERE -->
<intent-filter android:icon="YOUR_ICON_LOCATION"
android:label="YOUR_APP_LABEL"
android:priority="1">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="content" />
<data android:scheme="http" />
<data android:scheme="file" />
<data android:mimeType="*/*" />
<!-- TODO: CHANGE FILE EXTENSION -->
<data android:pathPattern=".*\\.YOUR_FILE_EXTENSION" />
</intent-filter>
Next, set up your Android Method Channel. For this, in Kotlin it is done like so:
import io.flutter.embedding.android.FlutterActivity
import android.content.Intent
import android.os.Bundle
import androidx.annotation.NonNull
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity : FlutterActivity() {
// TODO: CHANGE METHOD CHANNEL NAME
private val CHANNEL = "YOUR_CHANNEL_NAME"
var openPath: String? = null
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
val channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
channel.setMethodCallHandler { call, result ->
when (call.method) {
"getOpenFileUrl" -> {
result.success(openPath)
}
else -> result.notImplemented()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handleOpenFileUrl(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleOpenFileUrl(intent)
}
private fun handleOpenFileUrl(intent: Intent?) {
val path = intent?.data?.path
if (path != null) {
openPath = path
}
}
}
Finally, set up a handler on the dart side of things. The following code has to be in a drawn Widget. I placed it into my MainView widget, as it is always drawn on app startup and is relatively high up in the Widget tree. (For me it is the first Widget outside the MaterialApp Widget)
class MainView extends StatefulWidget {
const MainView({Key key}) : super(key: key);
#override
_MainViewState createState() => _MainViewState();
}
class _MainViewState extends State<MainView> with WidgetsBindingObserver {
// TODO: CHANGE CHANNEL NAME
static const platform = const MethodChannel("YOUR_CHANNEL_NAME");
#override
void initState() {
super.initState();
getOpenFileUrl();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
super.dispose();
WidgetsBinding.instance.removeObserver(this);
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
getOpenFileUrl();
}
}
#override
Widget build(BuildContext context) {
// TODO: Implement Build Method
}
void getOpenFileUrl() async {
dynamic url = await platform.invokeMethod("getOpenFileUrl");
if (url != null) {
setState(() {
// TODO: DO SOMETING WITH THE FILE URL
});
}
}
}
After completely killing and restarting both my app and the external app (in my case the file manager) it worked.
I am working on a native Android widget in a Flutter App. In which there is refresh button, on click of that I have to call a method in the Flutter code. I am using Flutter Method Channel for the communication and it is working fine when app is in foreground. But it does not work when app is minimised or closed. I get error PlatformException(NO_ACTIVITY, null, null). Below is my code.
Android (AppWidgetProvider)
if (methodChannel == null && context != null) {
FlutterMain.startInitialization(context)
FlutterMain.ensureInitializationComplete(context, arrayOf())
// Instantiate a FlutterEngine.
val engine = FlutterEngine(context.applicationContext)
// Define a DartEntrypoint
val entrypoint: DartEntrypoint = DartEntrypoint.createDefault()
// Execute the DartEntrypoint within the FlutterEngine.
engine.dartExecutor.executeDartEntrypoint(entrypoint)
// Register Plugins when in background. When there
// is already an engine running, this will be ignored (although there will be some
// warnings in the log).
//GeneratedPluginRegistrant.registerWith(engine)
methodChannel = MethodChannel(engine.dartExecutor.binaryMessenger, MainActivity.CHANNEL)
}
methodChannel!!.invokeMethod("fetchNewData", "", object : MethodChannel.Result {
override fun notImplemented() {
Toast.makeText(context, "method not implemented", Toast.LENGTH_SHORT).show()
}
override fun error(errorCode: String?, errorMessage: String?, errorDetails: Any?) {
Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show()
}
override fun success(result: Any?) {
Toast.makeText(context, "success", Toast.LENGTH_SHORT).show()
}
})
Flutter
/// calling in main
static Future<void> attachListeners() async {
WidgetsFlutterBinding.ensureInitialized();
var bloc = new AqiCnDashboardBloc();
_channel.setMethodCallHandler((call) {
switch (call.method) {
case 'fetchNewData':
bloc.getAqiCn(false);
return null;
default:
throw MissingPluginException('notImplemented');
}
});
}
I am collecting information/discussion that redirects us to run flutter engine in background.
void callbackDispatcher() {
WidgetsFlutterBinding.ensureInitialized();
print("Our background job ran!");
}
void main() {
static const MethodChannel _channel = const MethodChannel("channel-name");
Future<void> initialize(final Function callbackDispatcher) async {
final callback = PluginUtilities.getCallbackHandle(callbackDispatcher);
await _channel.invokeMethod('initialize', callback.toRawHandle());
}
}
As stated here How to run Flutter in the background?
When a background job is started by native the Flutter engine is not active. So we are unable to run Dart.
Can Android/iOS starts the Flutter engine in the background?
Yes! We’ll first need to register a Dart callback function which will only be invoked whenever a background job is started by the native code.
This callback function is referred to as a callbackDispatcher.
Also please check out these stackoverflow discussions.
Flutter : Run an app as a background service
How to create a service in Flutter to make an app to run always in background?
How to create a Flutter background service that works also when app closed
Executing Dart in the Background with Flutter Plugins and Geofencing
You may start the Flutter Engine in the background by register a Dart callback function which will only be invoked whenever a background job is started in Flutter.
Try this.
https://medium.com/vrt-digital-studio/flutter-workmanager-81e0cfbd6f6e