Flutter GetX: the value passed in arguments is null on second page - android

When navigating from one page to another using GetX Flutter, the arguments passed from the first page are not received on the second page.
First Controller:
class LoginController extends GetxController {
var _loading = false.obs;
get loading => _loading.value;
set loading(value) {
_loading.value = value;
}
var _loginResponse = LoginResponse().obs;
late TextEditingController emailController, passwordController;
#override
void onInit() {
super.onInit();
emailController = TextEditingController();
passwordController = TextEditingController();
}
#override
void onReady() {
super.onReady();
}
#override
void onClose() {
super.onClose();
emailController.dispose();
passwordController.dispose();
}
Future<void> loginCall(
{required String email, required String password}) async {
if (!GetUtils.isEmail(email)) {
Get.snackbar('Invalid email', 'Please enter valid email',
snackPosition: SnackPosition.BOTTOM);
return;
}
if (password.length < 8) {
Get.snackbar('Invalid password', 'Minimum 8 chars',
snackPosition: SnackPosition.BOTTOM);
return;
}
_loading.value = true;
var result =
await NetworkRequest.loginCall(email: email, password: password);
result != null
? _loginResponse.value = result
: Get.snackbar(
'Something went wrong', 'Hold back and try again after sometime.',
snackPosition: SnackPosition.BOTTOM);
if(_loginResponse.value.responseCode == 1) {
**Get.offAll(GoogleAuthenticationScreen(), arguments: _loginResponse.value.responseData!.session.toString());**
}
_loading.value = false;
}
}
Second Controller:
class GoogleAuthenticationController extends GetxController {
var _loading = false.obs;
late TextEditingController pinController;
var _authResponse = GoogleTwoFactorResponse().obs;
#override
void onInit() {
super.onInit();
pinController = TextEditingController();
var session = Get.arguments.toString();
twoFactorCall(token: session);
}
#override
void onClose() {
super.onClose();
pinController.dispose();
}
Future<void> twoFactorCall({required String token}) async {
_loading.value = true;
var result = await NetworkRequest.googleAuthentication(token: token);
result != null
? _authResponse.value = result
: Get.snackbar(
'Something went wrong', 'Hold back and try again after sometime.',
snackPosition: SnackPosition.BOTTOM);
Get.snackbar(result!.responseMessage!, "",
snackPosition: SnackPosition.BOTTOM);
_loading.value = false;
}
}
Getx Version get: ^4.3.8
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.2.2, on macOS 11.4 20F71 darwin-x64, locale en-IN)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio (version 4.1)
[✓] Connected device (2 available)
• No issues found!

you can try
final session = Get.arguments;
outside of onInIt method. hope it will work

Related

call java code in the flutter ERROR"MissingPluginException(No implementation found for method Printy on channel com.flutter.epic/epic)"

how to call java code in the flutter
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 2.5.0, on Microsoft Windows [Version 10.0.19042.1237], locale en-US)
[√] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
[√] Chrome - develop for the web
[√] Android Studio (version 2020.3)
[√] IntelliJ IDEA Ultimate Edition (version 2020.3)
[√] VS Code (version 1.60.1)
[√] Connected device (3 available)
• No issues found!
Flutter Code
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const platform = MethodChannel("com.flutter.epic/epic");
#override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
child: Text('Get Battery Level'),
onPressed: () {
Printy();
},
),
);
}
void Printy() async {
String value = "fail";
try {
value = await platform.invokeMethod("Printy");
} catch(e) {
print(e);
}
print(value);
}
}
Java Code
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "com.flutter.epic/epic";
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(new FlutterEngine(this));
new MethodChannel(getFlutterEngine().getDartExecutor().getBinaryMessenger(),CHANNEL).setMethodCallHandler(new MethodChannel.MethodCallHandler() {
#Override
public void onMethodCall(#NonNull MethodCall call, #NonNull MethodChannel.Result result) {
if (call.method.equals("Printy")) {
result.success("success!!!");
}
}
});
}
}
when I click button return this:
I/flutter (12941): MissingPluginException(No implementation found for method Printy on channel com.flutter.epic/epic)
I/flutter (12941): fail
I also try this "https://flutter.dev/docs/development/platform-integration/platform-channels" step by step , but it dose not work in my computer.
how to call java code in the flutter
please , help
The syntax aren't wrong anywhere. Kotlin is the default language unless you explicitly put as Java. I guess you didn't set Java for yours while creating the app. If that's the case then you can convert your project to Java by following Sheruan Bashar's answer.

I'm trying to Sign In with Google in Flutter, the problem is after filling the Sign In Credentials it doesn't move to next page, and returns to login

I'm trying to Sign In with google in Flutter, the problem is after filling the Sign In Credentials it doesn't move to next page, and returns to login page. Also it doesn't save any data in Cloud Firestore. But it adds that email login user into users Authentication field in Firebase. I tried this with different codes to solve this but nothing happens.
Following is the work I've done till now.
**Flutter Doctor:**
C:\Users\Nouma>flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 2.2.2, on Microsoft Windows [Version 10.0.19042.1052], locale en-PK)
[√] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[√] Chrome - develop for the web
[√] Android Studio (version 4.1.0)
[√] VS Code (version 1.57.1)
[√] Connected device (3 available)
• No issues found!
C:\Users\Nouma>
**pubspec.yaml dependencies:**
dependencies:
flutter:
sdk: flutter
firebase_auth: ^2.0.0
firebase_core: ^1.3.0
auth_buttons: ^1.0.1+4
cloud_firestore: ^2.3.0
firebase_storage: ^9.0.0
google_sign_in: ^5.0.4
firebase_analytics: ^8.1.2
firebase: ^9.0.1
**users.dart class where GoogleSignIn function is called:**
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
final FirebaseAuth _firebaseAuth=FirebaseAuth.instance;
final GoogleSignIn googleSignIn=GoogleSignIn();
late String name;
late String email;
late String imageurl;
Future<String> signInWithGoogle() async {
final GoogleSignInAccount? googleSignInAccount = await GoogleSignIn()
.signIn();
final GoogleSignInAuthentication googleSignInAuthentication = await googleSignInAccount!
.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleSignInAuthentication.accessToken,
idToken: googleSignInAuthentication.idToken,
);
final UserCredential result = await _firebaseAuth.signInWithCredential(
credential);
final User? firebaseUser = result.user;
assert(firebaseUser!.email != null);
assert(firebaseUser!.displayName != null);
assert(firebaseUser!.photoURL != null);
name = firebaseUser!.email!;
email = firebaseUser.displayName!;
imageurl = firebaseUser.photoURL!;
final User? currentuser = await _firebaseAuth.currentUser;
assert(firebaseUser.uid == currentuser!.uid);
return 'SignInWithGoogle Succeeded: $firebaseUser';
//return await FirebaseAuth.instance.signInWithCredential(credential);
}
**google.dart class, here GoogleAuthButton with GoogleSignIn function is called:**
import 'package:authentication/users.dart';
import 'package:flutter/material.dart';
import 'package:auth_buttons/auth_buttons.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
final auth=FirebaseFirestore.instance;
class Google extends StatelessWidget {
const Google({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: GoogleAuthButton(darkMode: true,
onPressed: (){
signInWithGoogle().then((googlesignedin)
{
auth.collection('users').doc('auth').collection('GoogleUsers')
.add({
'email':email, 'image':imageurl, 'name':name,}).then((value){
if(googlesignedin!=null)
{
Navigator.pushNamed(context,'/homepage');
}
}
).catchError((e)
{
print(e);
});
}
).catchError((e)
{
print(e);
}
);
})
);
}
}
Future loginGoogle({Function? success, ValueChanged<String>? fail}) async {
try {
GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
GoogleSignInAuthentication? googleAuth = await googleUser!.authentication;
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken, idToken: googleAuth.idToken);
final result = await auth.signInWithCredential(credential);
if (!result.user!.emailVerified) {
print('Login Cancelled');
} else if (result.additionalUserInfo!.isNewUser) {
// CRAETE USER
print('SUCCESSFULYY CREATED USER');
} else {
print('ALL READY EXISTS THE USER');
}
} catch (e) {
print(e.toString());
}
}
##################################################################################
I HAVE MADE THAT FUNCTION FOR GOOGLE SIGN IN WITH NULL-SAFETY PACKAGES I HOPE IT WILL HELP YOU BROTHER

Sleepless process on Ionic5, Capacitor, Android

I have an ionic5 app with Capacitor that I'd like to deploy on android. When the app starts I'm getting a list of reminders (from API) I'd like to schedule to be shown as local notifications during the day. After deploying it to my device (by .apk file) is working fine when it's open but after some time when the phone is not in use it's getting sleep and no notification appears. What would be the best way to solve that case? This is my last code with BackgroundTask but it doesn't work anyway.
import { Injectable } from "#angular/core";
import { Job } from "src/entities/respons/_respons";
import { Plugins } from '#capacitor/core';
const { LocalNotifications, BackgroundTask } = Plugins;
#Injectable({
providedIn: 'root',
})
export class NotificationsService {
constructor() {
LocalNotifications.requestPermission();
console.log(` Initialized on ${new Date().toLocaleString()}`);
}
async setup(jobs: Job[]) {
let id: number = 0;
let taskId = BackgroundTask.beforeExit(async () => {
let toSchedule = jobs.filter(e => (e.isActive || e.isFuture) && !e.isNotified);
id = toSchedule.length;
console.log(`Setup, count ${id};`)
let notificationInterval = setInterval(async () => {
let toNotify = jobs.filter(e => e.isActive && !e.isNotified);
if (toNotify.length > 0) {
let logger: string = '';
toNotify.forEach(async job => {
let d = new Date(job.since);
logger += `[${d.toLocaleTimeString()} ${job.name}], `;
await LocalNotifications.schedule({
notifications: [
{
id: job.id,
title: `${job.name} ${d.toLocaleTimeString()}`,
body: job.body,
iconColor: '#0081ca'
}
]
});
job.isNotified = true;
id--;
});
console.log(`Tick on ${new Date(Date.now()).toTimeString()} , count ${toNotify.length}; Scheduled: ${logger}`)
}
if (id <= 0) {
console.log(`Task finished ${id}; no.:${taskId}`);
clearInterval(notificationInterval);
BackgroundTask.finish({
taskId
});
} else {
console.log(`Task in progress ${id}; no.:${taskId}`,)
}
}, 60000)
});
}
}

Google Map rendering on laptop but not on mobile in Ionic app

I am trying to display Google Maps in my Ionic app & center on my current location.
When I run this on my laptop, it displays as expected.
But when I try to run it on my mobile device, the map isn't rendering.
Here is the code:
ngAfterViewInit() {
this.locateUser();
console.log('My Coords', this.coordinates);
this.getGoogleMaps().then(googleMaps => {
const mapEl = this.mapElementRef.nativeElement;
const map = new googleMaps.Map(mapEl, {
center: this.coordinates,
zoom: 16
});
googleMaps.event.addListenerOnce(map, 'idle', () => {
this.renderer.addClass(mapEl, 'visible');
});
}).catch(err => {
console.log('Google Maps error:', err);
});
}
private getGoogleMaps() {
const win = window as any;
const googleModule = win.google;
if (googleModule && googleModule.maps) {
return Promise.resolve(googleModule.maps);
}
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = 'https://maps.googleapis.com/maps/api/js?key=myAPIKey';
script.async = true;
script.defer = true;
document.body.appendChild(script);
script.onload = () => {
const loadedGoogleModule = win.google;
if (loadedGoogleModule && loadedGoogleModule.maps) {
resolve(loadedGoogleModule.maps);
} else {
reject('Google Maps SDK not available.');
}
};
});
}
private locateUser() {
if (!Capacitor.isPluginAvailable('Geolocation')) {
this.showErrorAlert();
return;
}
Plugins.Geolocation.getCurrentPosition()
.then(geoPosition => {
this.coordinates = {
lat: geoPosition.coords.latitude,
lng: geoPosition.coords.longitude
};
console.log(this.coordinates);
})
.catch(err => {
this.showErrorAlert();
});
}
Can someone please tell me why this is working on my laptop, but not on my mobile?
Also, here are the steps I'm taking to run on the mobile:
ionic build
ionic capacitor run android
Then I run the app in Android Studio.

Intergrating native Twilio Android SDK with Flutter

I am trying to create Voice Over IP (VOIP) mobile application using flutter.I haven't seen an implementation for a flutter plugin for twilio voice api so i intergrated my application with the native android voice api using MethodChannel.The twilio SDK doesnt seem like it intergrated correctly i cant access the twilio classes and methods in scripts. These are the errors i get.
Running Gradle task 'assembleDebug'...
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:23: error: package android.support.annotation does not exist
import android.support.annotation.NonNull;
^
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:295: error: cannot find symbol
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
^
symbol: class NonNull
location: class MainActivity
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:295: error: cannot find symbol
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
^
symbol: class NonNull
location: class MainActivity
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:117: error: cannot find symbol
soundPoolManager = SoundPoolManager.getInstance(this.MainActivity);
^
symbol: variable MainActivity
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:186: error: cannot find symbol
public void onReconnecting(#NonNull Call call, #NonNull CallException callException) {
^
symbol: class NonNull
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:186: error: cannot find symbol
public void onReconnecting(#NonNull Call call, #NonNull CallException callException) {
^
symbol: class NonNull
/home/kudziesimz/voip20/android/app/src/main/java /com/workerbees/voip20/MainActivity.java:191: error: cannot find symbol
public void onReconnected(#NonNull Call call) {
^
symbol: class NonNull
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:279: error: cannot find symbol
int resultMic = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO);
^
symbol: variable ContextCompat
location: class MainActivity
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:284: error: method shouldShowRequestPermissionRationale in class Activity cannot be applied to given types;
if (MainActivity.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) {
^
required: String
found: MainActivity,String
reason: actual and formal argument lists differ in length
/home/kudziesimz/voip20/android/app/src/main/java/com/workerbees /voip20/MainActivity.java:287: error: method requestPermissions in class Activity cannot be applied to given types;
MainActivity.requestPermissions(
^
required: String[],int
found: MainActivity,String[],int
reason: actual and formal argument lists differ in length
Note: /home/kudziesimz/voip20/android/app/src/main/java /com/workerbees/voip20/MainActivity.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
10 errors
I followed the voice-quickstart-android guide shown here https://github.com/twilio/voice-quickstart-android
here is my code:main.dart
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
//This is a test application which allows clients to make Voice Over The Internet Cal
void main() => runApp(MaterialApp(
home: MyApp(),
));
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
static const platform = const MethodChannel("com.voip.call_management/calls");
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Call Management"),
),
bottomNavigationBar: Center(
child: IconButton(
icon: Icon(Icons.phone),
onPressed: () {
_makeCall;
}),
),
);
}
Future<void> _makeCall() async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: Row(
children: <Widget>[
Text('Call'),
Icon(
Icons.phone,
color: Colors.blue,
)
],
),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
TextField(
decoration: InputDecoration(
hintText: "client identity or phone number"),
),
SizedBox(
height: 20,
),
Text(
'Dial a client name or number.Leaving the field empty will result in an automated response.'),
],
),
),
actions: <Widget>[
FlatButton(
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
IconButton(icon: Icon(Icons.phone), onPressed:()async {
try {
final result = await platform.invokeMethod("makecall");
} on PlatformException catch (e) {
print(e.message);
}
})
],
);
},
);
}
}
MainActivity.java
package com.workerbees.voip20;
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
//javacode imports
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.util.Log;
import com.google.firebase.iid.FirebaseInstanceId;
import com.koushikdutta.async.future.FutureCallback;
import com.koushikdutta.ion.Ion;
import com.twilio.voice.Call;
import com.twilio.voice.CallException;
import com.twilio.voice.CallInvite;
import com.twilio.voice.ConnectOptions;
import com.twilio.voice.RegistrationException;
import com.twilio.voice.RegistrationListener;
import com.twilio.voice.Voice;
import java.util.HashMap;
//sound pool imports
import android.media.SoundPool;
import static android.content.Context.AUDIO_SERVICE;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "com.workerbees.voip/calls"; // MethodChannel Declaration
private static final String TAG = "VoiceActivity";
private static String identity = "alice";
private static String contact;
/*
* You must provide the URL to the publicly accessible Twilio access token server route
*
* For example: https://myurl.io/accessToken
*
* If your token server is written in PHP, TWILIO_ACCESS_TOKEN_SERVER_URL needs .php extension at the end.
*
* For example : https://myurl.io/accessToken.php
*/
private static final String TWILIO_ACCESS_TOKEN_SERVER_URL = "https://bd107744.ngrok.io/accessToken";
private static final int MIC_PERMISSION_REQUEST_CODE = 1;
private String accessToken;
private AudioManager audioManager;
private int savedAudioMode = AudioManager.MODE_INVALID;
// Empty HashMap, never populated for the Quickstart
HashMap<String, String> params = new HashMap<>();
private SoundPoolManager soundPoolManager;
private Call activeCall;
Call.Listener callListener = callListener();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodCallHandler() {
#Override
public void onMethodCall(MethodCall call, Result result) {
// Note: this method is invoked on the main thread.
// TODO
if(call.method.equals("makecall")){
params.put("to", contact);
ConnectOptions connectOptions = new ConnectOptions.Builder(accessToken)
.params(params)
.build();
activeCall = Voice.connect(MainActivity.this, connectOptions, callListener);
}
else if(call.method.equals("hangup")){
disconnect();
}
else if(call.method.equals("mute")){
mute();
}
else if (call.method.equals("hold")){
hold();
}
else{
Log.d(TAG,"invalid API call");
}
}
});
soundPoolManager = SoundPoolManager.getInstance(this.MainActivity);
/*
* Needed for setting/abandoning audio focus during a call
*/
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.setSpeakerphoneOn(true);
/*
* Enable changing the volume using the up/down keys during a conversation
*/
setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
/*
* Displays a call dialog if the intent contains a call invite
*/
//handleIncomingCallIntent(getIntent());
/*
* Ensure the microphone permission is enabled
*/
if (!checkPermissionForMicrophone()) {
requestPermissionForMicrophone();
} else {
retrieveAccessToken();
}
}
private Call.Listener callListener() {
return new Call.Listener() {
/*
* This callback is emitted once before the Call.Listener.onConnected() callback when
* the callee is being alerted of a Call. The behavior of this callback is determined by
* the answerOnBridge flag provided in the Dial verb of your TwiML application
* associated with this client. If the answerOnBridge flag is false, which is the
* default, the Call.Listener.onConnected() callback will be emitted immediately after
* Call.Listener.onRinging(). If the answerOnBridge flag is true, this will cause the
* call to emit the onConnected callback only after the call is answered.
* See answeronbridge for more details on how to use it with the Dial TwiML verb. If the
* twiML response contains a Say verb, then the call will emit the
* Call.Listener.onConnected callback immediately after Call.Listener.onRinging() is
* raised, irrespective of the value of answerOnBridge being set to true or false
*/
#Override
public void onRinging(Call call) {
Log.d(TAG, "Ringing");
}
#Override
public void onConnectFailure(Call call, CallException error) {
setAudioFocus(false);
Log.d(TAG, "Connect failure");
String message = String.format("Call Error: %d, %s", error.getErrorCode(), error.getMessage());
Log.e(TAG, message);
}
#Override
public void onConnected(Call call) {
setAudioFocus(true);
Log.d(TAG, "Connected");
activeCall = call;
}
#Override
public void onReconnecting(#NonNull Call call, #NonNull CallException callException) {
Log.d(TAG, "onReconnecting");
}
#Override
public void onReconnected(#NonNull Call call) {
Log.d(TAG, "onReconnected");
}
#Override
public void onDisconnected(Call call, CallException error) {
setAudioFocus(false);
Log.d(TAG, "Disconnected");
if (error != null) {
String message = String.format("Call Error: %d, %s", error.getErrorCode(), error.getMessage());
Log.e(TAG, message);
}
}
};
}
private void disconnect() {
if (activeCall != null) {
activeCall.disconnect();
activeCall = null;
}
}
private void hold() {
if (activeCall != null) {
boolean hold = !activeCall.isOnHold();
activeCall.hold(hold);
}
}
private void mute() {
if (activeCall != null) {
boolean mute = !activeCall.isMuted();
activeCall.mute(mute);
}
}
private void setAudioFocus(boolean setFocus) {
if (audioManager != null) {
if (setFocus) {
savedAudioMode = audioManager.getMode();
// Request audio focus before making any device switch.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AudioAttributes playbackAttributes = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build();
AudioFocusRequest focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
.setAudioAttributes(playbackAttributes)
.setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(new AudioManager.OnAudioFocusChangeListener() {
#Override
public void onAudioFocusChange(int i) {
}
})
.build();
audioManager.requestAudioFocus(focusRequest);
} else {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.FROYO) {
int focusRequestResult = audioManager.requestAudioFocus(
new AudioManager.OnAudioFocusChangeListener() {
#Override
public void onAudioFocusChange(int focusChange)
{
}
}, AudioManager.STREAM_VOICE_CALL,
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
}
}
/*
* Start by setting MODE_IN_COMMUNICATION as default audio mode. It is
* required to be in this mode when playout and/or recording starts for
* best possible VoIP performance. Some devices have difficulties with speaker mode
* if this is not set.
*/
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
} else {
audioManager.setMode(savedAudioMode);
audioManager.abandonAudioFocus(null);
}
}
}
private boolean checkPermissionForMicrophone() {
int resultMic = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO);
return resultMic == PackageManager.PERMISSION_GRANTED;
}
private void requestPermissionForMicrophone() {
if (MainActivity.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) {
} else {
MainActivity.requestPermissions(
this,
new String[]{Manifest.permission.RECORD_AUDIO},
MIC_PERMISSION_REQUEST_CODE);
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
/*
* Check if microphone permissions is granted
*/
if (requestCode == MIC_PERMISSION_REQUEST_CODE && permissions.length > 0) {
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Microphone permissions needed. Please allow in your application settings.");
} else {
retrieveAccessToken();
}
}
}
/*
* Get an access token from your Twilio access token server
*/
private void retrieveAccessToken() {
Ion.with(this).load(TWILIO_ACCESS_TOKEN_SERVER_URL + "?identity=" + identity).asString().setCallback(new FutureCallback<String>() {
#Override
public void onCompleted(Exception e, String accessToken) {
if (e == null) {
Log.d(TAG, "Access token: " + accessToken);
MainActivity.this.accessToken = accessToken;
} else {
Log.d(TAG, "Registration failed");
}
}
});
}
}
class SoundPoolManager {
private boolean playing = false;
private boolean loaded = false;
private boolean playingCalled = false;
private float actualVolume;
private float maxVolume;
private float volume;
private AudioManager audioManager;
private SoundPool soundPool;
private int ringingSoundId;
private int ringingStreamId;
private int disconnectSoundId;
private static SoundPoolManager instance;
private SoundPoolManager(Context context) {
// AudioManager audio settings for adjusting the volume
audioManager = (AudioManager) context.getSystemService(AUDIO_SERVICE);
actualVolume = (float) audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
maxVolume = (float) audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
volume = actualVolume / maxVolume;
// Load the sounds
int maxStreams = 1;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
soundPool = new SoundPool.Builder()
.setMaxStreams(maxStreams)
.build();
} else {
soundPool = new SoundPool(maxStreams, AudioManager.STREAM_MUSIC, 0);
}
soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
#Override
public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
loaded = true;
if (playingCalled) {
playRinging();
playingCalled = false;
}
}
});
ringingSoundId = soundPool.load(context, R.raw.incoming, 1);
disconnectSoundId = soundPool.load(context, R.raw.disconnect, 1);
}
public static SoundPoolManager getInstance(Context context) {
if (instance == null) {
instance = new SoundPoolManager(context);
}
return instance;
}
public void playRinging() {
if (loaded && !playing) {
ringingStreamId = soundPool.play(ringingSoundId, volume, volume, 1, -1, 1f);
playing = true;
} else {
playingCalled = true;
}
}
public void stopRinging() {
if (playing) {
soundPool.stop(ringingStreamId);
playing = false;
}
}
public void playDisconnect() {
if (loaded && !playing) {
soundPool.play(disconnectSoundId, volume, volume, 1, 0, 1f);
playing = false;
}
}
public void release() {
if (soundPool != null) {
soundPool.unload(ringingSoundId);
soundPool.unload(disconnectSoundId);
soundPool.release();
soundPool = null;
}
instance = null;
}
}
This my build.gradle
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle /flutter.gradle"
android {
compileSdkVersion 28
lintOptions {
disable 'InvalidPackage'
}
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.workerbees.voip20"
minSdkVersion 16
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
}
// Specify that we want to split up the APK based on ABI
splits {
abi {
// Enable ABI split
enable true
// Clear list of ABIs
reset()
// Specify each architecture currently supported by the Video SDK
include "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
// Specify that we do not want an additional universal SDK
universalApk false
}
}
}
}
flutter {
source '../..'
}
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
implementation 'com.twilio:voice-android:4.5.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support:support-media-compat:28.0.0'
implementation 'com.android.support:animated-vector-drawable:28.0.0'
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.squareup.retrofit:retrofit:1.9.0'
implementation 'com.koushikdutta.ion:ion:2.1.8'
implementation 'com.google.firebase:firebase-messaging:17.6.0'
implementation 'com.android.support:support-annotations:28.0.0'
}
this is my build.gradle from my gradle folder
buildscript {
repositories {
jcenter()
maven {
url 'https://maven.google.com/'
name 'Google'
}
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
}
}
allprojects {
repositories {
google()
jcenter()
mavenCentral()
maven {
url 'https://maven.google.com/'
name 'Google'
}
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Are you still having this issue? Following Flutter platform channels guide, I was able to use Twilio Android SDK without issues. I integrated the bare minimum components needed for Twilio in this demo based from Twilio's Android quickstart.
main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
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();
}
class _MyHomePageState extends State<MyHomePage> {
static const platform = const MethodChannel('samples.flutter.dev/twilio');
Future<void> callTwilio() async{
try {
final String result = await platform.invokeMethod('callTwilio');
debugPrint('Result: $result');
} on PlatformException catch (e) {
debugPrint('Failed: ${e.message}.');
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Hello',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => callTwilio(),
tooltip: 'Call',
child: Icon(Icons.phone),
),
);
}
}
android/app/src/main/kotlin/{PACKAGE_NAME}/MainActivity.kt
class MainActivity : FlutterActivity() {
private val CHANNEL = "samples.flutter.dev/twilio"
private val TAG = "MainActivity"
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "callTwilio") {
executeTwilioVoiceCall()
result.success("Hello from Android")
} else {
result.notImplemented()
}
}
}
private val accessToken = ""
var params = HashMap<String, String>()
var callListener: Call.Listener = callListener()
fun executeTwilioVoiceCall(){
val connectOptions = ConnectOptions.Builder(accessToken)
.params(params)
.build()
Voice.connect(this, connectOptions, callListener)
}
private fun callListener(): Call.Listener {
return object : Call.Listener {
override fun onRinging(call: Call) {
Log.d(TAG, "Ringing")
}
override fun onConnectFailure(call: Call, error: CallException) {
Log.d(TAG, "Connect failure")
}
override fun onConnected(call: Call) {
Log.d(TAG, "Connected")
}
override fun onReconnecting(call: Call, callException: CallException) {
Log.d(TAG, "onReconnecting")
}
override fun onReconnected(call: Call) {
Log.d(TAG, "onReconnected")
}
override fun onDisconnected(call: Call, error: CallException?) {
Log.d(TAG, "Disconnected")
}
override fun onCallQualityWarningsChanged(call: Call,
currentWarnings: MutableSet<CallQualityWarning>,
previousWarnings: MutableSet<CallQualityWarning>) {
if (previousWarnings.size > 1) {
val intersection: MutableSet<CallQualityWarning> = HashSet(currentWarnings)
currentWarnings.removeAll(previousWarnings)
intersection.retainAll(previousWarnings)
previousWarnings.removeAll(intersection)
}
val message = String.format(
Locale.US,
"Newly raised warnings: $currentWarnings Clear warnings $previousWarnings")
Log.e(TAG, message)
}
}
}
}
As for the dependencies in Android, I've added these on the build.gradle configs
android/build.gradle
ext.versions = [
'voiceAndroid' : '5.6.2',
'audioSwitch' : '1.1.0',
]
android/app/build.grade
dependencies {
...
implementation "com.twilio:audioswitch:${versions.audioSwitch}"
implementation "com.twilio:voice-android:${versions.voiceAndroid}"
}
Here's my flutter doctor verbose logs for reference
[✓] Flutter (Channel master, 1.26.0-2.0.pre.281, on macOS 11.1 20C69 darwin-x64)
• Flutter version 1.26.0-2.0.pre.281
• Framework revision 4d5db88998 (3 weeks ago), 2021-01-11 10:29:26 -0800
• Engine revision d5cacaa3a6
• Dart version 2.12.0 (build 2.12.0-211.0.dev)
[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
• Platform android-30, build-tools 29.0.2
• Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)
• All Android licenses accepted.
[✓] Xcode - develop for iOS and macOS (Xcode 12.0.1)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Xcode 12.0.1, Build version 12A7300
• CocoaPods version 1.10.0
[✓] Chrome - develop for the web
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Android Studio (version 4.1)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6915495)
[✓] VS Code (version 1.52.1)
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension version 3.18.1
[✓] Connected device (2 available)
• AOSP on IA Emulator (mobile) • emulator-5554 • android-x86 • Android 9 (API 28) (emulator)
• Chrome (web) • chrome • web-javascript • Google Chrome 88.0.4324.96
• No issues found!
Here's how the sample app looks when run. Logs throw "Connect failure" and "Forbidden:403" errors since the API keys set are invalid, but this proves that Twilio Android SDK is functional through Flutter platform channels.
You can also check pub.dev for Twilio Flutter plugins made by the community that may fit your use case.

Categories

Resources