I am very new to flutter+dart framework. I am trying to understand how EventChannel works. I have set up EventChannel to capture the number of an incoming call.
On the android side, I have set up an BroadcastReceiver as follows.
public class CallEventHandler extends BroadcastReceiver implements EventChannel.StreamHandler {
private static final String TAG = "[SAMPLE]";
private static final int NUMBER_LEN = 10;
private EventChannel.EventSink eventSink = null;
private Activity activity = null;
public CallEventHandler(Activity activity) {
this.activity = activity;
}
#Override
public void onListen(Object arguments, EventChannel.EventSink events) {
Log.i(TAG, "[onListen] setting up events");
eventSink = events;
}
#Override
public void onCancel(Object arguments) {
Log.i(TAG, "[onCancel] cancel events");
eventSink = null;
activity = null;
}
#Override
public void onReceive(Context context, Intent intent) {
try {
String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
String incomingNumber = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
if(state.equals(TelephonyManager.EXTRA_STATE_RINGING)) {
if(incomingNumber != null) {
Log.i(TAG, "[CallEventHandler] Incoming number : " + incomingNumber);
if(incomingNumber.length() > NUMBER_LEN) {
incomingNumber = incomingNumber.substring(incomingNumber.length() - NUMBER_LEN, incomingNumber.length());
Log.i(TAG, "[CallEventHandler] Incoming number after : " + incomingNumber);
if(activity != null) {
String finalIncomingNumber = incomingNumber;
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
if(eventSink != null) {
Log.i(TAG, "[CallEventHandler] HERESSSSS : " + finalIncomingNumber);
eventSink.success(finalIncomingNumber);
}
}
});
}
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
In the onReceive method, I am getting the incoming number and I am sending it to EventSink.
In my MainActivity I am setting up the CallEventHandler as follows:
private final String eventId = "SAMPLE_ID";
private CallEventHandler handler = new CallEventHandler(this);
#Override
public void onStart() {
super.onStart();
...
registerReceiver(handler, filter);
}
#Override
public void onStop() {
super.onStop();
unregisterReceiver(handler);
}
#Override
public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
new EventChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), eventId)
.setStreamHandler(handler);
}
On the Flutter side, the code is as follows:
class EventHandler {
static const String TAG = "[SAMPLE]";
final String _eventId = "SAMPLE_ID";
EventChannel? _evtChannel;
Stream<String>? _evtStream;
EventHandler() {
debugPrint(TAG + " Setting up EventHandler");
_evtChannel = EventChannel(_eventId);
_evtStream = _evtChannel?.receiveBroadcastStream().distinct().map((dynamic
event) => getString(event as String));
}
void startListening(void Function(String data)? onData) {
debugPrint(TAG + " starting listening");
_evtStream?.listen((data) {
debugPrint(TAG + " In listening");
onData!(data);
});
}
}
In my UI code, I have a StatefulWidget (MySamplePage) where I am registering my callback when the call is received
void initState() {
widget.handler.startListening((incomingNumber) {
debugPrint(_tag + " data : $incomingNumber");
...
});
}
In my stateful home page build method, I initialize the handler in initState and added a route in build method
class _HomeScreenState extends State<HomeScreen> {
#override
void initState() {
super.initState();
debugPrint(_tag + "initState");
_handler = EventHandler();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'/caller': (context) => MySamplePage(
handler: _handler
),
},
...
);
}
}
The issue I am facing is that, when the widget is opened I am receiving the first incoming call, as expected. But if I make another call, then that second call is not captured by the stream. If I press the back button, and reopen the Widget everything works as expected, the first incoming call is printed in the console. I know the the Android code is sending the event from the onReceive method (The `HERESSSSS' line is printed every time), but the flutter stream is not getting the values. I am not sure what I am doing wrong here. Can anyone please help?
My log is
I/flutter (11836): [SAMPLE][HomeScreen]initState
I/flutter (11836): [SAMPLE][EventHandler] Setting up EventHandler
V/AutofillManager(11836): requestHideFillUi(null): anchor = null
I/flutter (11836): [SAMPLE][EventHandler] starting listening
I/[SAMPLE] (11836): [onListen] setting up events
I/[SAMPLE] (11836): [CallEventHandler] Receiver start
I/[SAMPLE] (11836): [CallEventHandler] Receiver start
I/[SAMPLE] (11836): [CallEventHandler] Incoming number : +91XXXXXXXXXX
I/[SAMPLE] (11836): [CallEventHandler] Incoming number after : XXXXXXXXXX
I/[SAMPLE] (11836): [CallEventHandler] HERESSSSS : XXXXXXXXXX
I/flutter (11836): [SAMPLE][EventHandler] In listening
I/flutter (11836): [SAMPLE] data : XXXXXXXXXX
In the subsequent incoming calls, the last line is not printed
Thank you
Ok, I have managed to resolve it, but don't know if this is the correct approach. The issue is that MySamplePage is a StatefulWidget, And I am calling setState in its State object. That might be the reason it's unable to listen to the stream anymore. I have called startListening is the setState method and changed the code accordingly (remove the previous subscription and re-listen to the stream)
void startListening(void Function(String data)? onData) {
debugPrint(TAG + " starting listening");
if(_subscription != null) {
_subscription?.cancel();
_subscription = null;
}
_subscription ??= _evtStream?.listen((data) {
debugPrint(TAG + " In listening");
onData!(data);
});
}
Here _subscription is a variable of type StreamSubscription<String>?. Hope this answer is helpful. And I should have posted complete code earlier.
Related
From the Flutter side, using the PlatformChannel, I am navigating to an Android Java activity, and doing some processes.
The activity successfully opens and I'm able to do the functionality and have the final result of it.
How may I navigate back to the Flutter side to a specific page and pass a value?
P.S.: without going back to the same page and then redirecting to the
next page.
On the Flutter side:
I have these variables
/// Filters Method Channel
final filtersChannel = const MethodChannel('flutter.native/filters');
/// Filters Method Channel
final filtersResultChannel = const MethodChannel("flutter.native/result_filters");
I have a floatingActionButton with this function which invokes a MethodChannel
Future<void> startNewActivity() async {
try {
await filtersChannel.invokeMethod('open_filters');
} on PlatformException catch (e) {
debugPrint("Failed to Invoke: '${e.message}'.");
}
}
On the MainActivity.java
On the protected void onCreate(#Nullable Bundle savedInstanceState) function, I'm starting an activity which has the AR video recording like this:
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(this, FiltersActivity.class);
startActivity(intent);
}
On the FiltersActivity.java
On the public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) function
I’m defining and invoking my two channels:
The flutter.native/result_filters channel which builds the UI and
the functionality.
The flutter.native/filters channel which returns the final result.
Here:
#Override
public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
String resultFiltersChannelIdentifier = "flutter.native/result_filters";
filtersResultChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), resultFiltersChannelIdentifier);
String filtersChannelIdentifier = "flutter.native/filters";
MethodChannel filtersChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), filtersChannelIdentifier);
filtersChannel.setMethodCallHandler(this::filtersMethodCallHandler);
}
Then, the flutter.native/filters displays the UI using the filtersMethodCallHandler function. Here:
private void filtersMethodCallHandler(MethodCall methodCall, MethodChannel.Result result) {
if (methodCall.method.equals("open_filters")) {
openUI();
} else {
result.notImplemented();
}
}
In the openUI function, I'm assigning the record button a function, here:
recordButton.setOnClickListener(this::toggleRecording);
And here's the toggleRecording function:
public void toggleRecording(View unusedView) {
boolean recording = videoRecorder.onToggleRecord();
if (recording) {
recordButton.setImageResource(R.drawable.round_stop);
Toast.makeText(this, "Started Recording", Toast.LENGTH_SHORT).show();
} else {
recordButton.setImageResource(R.drawable.round_videocam);
Toast.makeText(this, "Recording Stopped", Toast.LENGTH_SHORT).show();
videoPath = videoRecorder.getVideoPath().getAbsolutePath();
Toast.makeText(this, "Video saved: " + videoPath, Toast.LENGTH_SHORT).show();
Log.d(TAG, "Video saved: " + videoPath);
// Send notification of updated content.
ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.TITLE, "Sceneform Video");
values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
values.put(MediaStore.Video.Media.DATA, videoPath);
getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
filtersResultChannel.invokeMethod("filters_result", videoPath);
finish();
}
}
As shown above, I'm invoking the filters_result method for the filtersResultChannel channel and I'm adding the videoPath to it.
And then, I'm calling the finish(); method to close the FiltersActivity and return back to the MainAvtivity which successfully returns me to the Flutter page!
BACK to the Flutter side,
I'm listening to the filtersResultChannel like this:
#override
void initState() {
super.initState();
filtersResultChannel.setMethodCallHandler(_filtersResultHandler);
}
Future _filtersResultHandler(MethodCall methodCall) async {
if (methodCall.method == "filters_result") {
final videoPath = methodCall.arguments;
if (videoPath != null && videoPath.length >= 0) {
SchedulerBinding.instance.addPostFrameCallback((_) {
debugPrint("YES YES YES => $videoPath");
setState(() {
reportStatus = videoPath;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => VideoShow(clipPath: videoPath),
),
);
});
});
}
return null;
} else {
return null;
}
}
As shown above, I have a debugPrint statement, this statement prints the returned videoPath from the filtersResultChannel
<--------->
THE PROBLEM
<--------->
Even though I'm successfully getting the videoPath value and successfully returning back to the Flutter page, I'm NOT able to use it!!
The setState(); doesn't update the UI NOR navigate to the next screen, the VideoShow screen!
HOW MAY I FIX SUCH AN ISSUE?
I want to send value from string (distance to obstacle) to my TextView in main activity.
I tried to use Handler, but still not working (crash) or receive nothing.
A part code which receive data from HC-05 (screen where you see in debug value assignet to variable)
enter image description here
#Override
public void run() {
byte[] buffer = new byte[1024];
int bytes;
while(true){
try {
bytes = inputStream.read(buffer);
final String comingMsg = new String(buffer,0,bytes);
Log.d(TAG,"InputStream: " + comingMsg);
/*mHandler2.post(new Runnable() {
#Override
public void run() {
Message message = new Message();
message.obj = comingMsg;
mHandler2.sendMessage(message);
}
});*/
}catch (IOException e){
Log.e(TAG,"Write: Error reading input." + e.getMessage());
active=false;
break;
}
}
}
Here It's parts of code from MainActivity where I tried put something to get values from service.
[I add, that for this moment i want to see something values from bluetooth in textView. Later I want to create parse string and send custom text to custom TextView - example: FL: (Front Left)- to one textView, FR: (Front Right) - to second textView]
There is method implementThreads(), because I wanted to do 6 Threads to 6 TextView which every time is refreshing value from string in Services (there I tried get value from Bluetooth Service)
Log.d(TAG,"Check intent - result");
if(getIntent().getIntExtra("result",0)==RESULT_OK){
mDevice = getIntent().getExtras().getParcelable("bonded device");
myBluetoothService = new MyBluetoothService(getApplicationContext());
startConnection(mDevice,MY_UUID);
Log.d(TAG,"Check is active service");
checkIfActive();
}
Log.d(TAG,"Check intent - connect_to_paired");
if(getIntent().getIntExtra("connect_to_paired",0)==RESULT_OK){
mDevice = getIntent().getExtras().getParcelable("bonded_paired_device");
myBluetoothService = new MyBluetoothService(getApplicationContext());
startConnection(mDevice,MY_UUID);
Log.d(TAG,"Check is active service");
checkIfActive();
}
}
#Override
public void onStart(){
super.onStart();
myBluetoothService = new MyBluetoothService(getApplicationContext());
}
public void checkIfActive(){
Log.d(TAG,"CheckIfActive: Started");
if(myBluetoothService.active){
Log.d(TAG,"CheckIfActive: Running method implementThreads()");
implementThreads();
}
}
public void implementThreads(){
Log.d(TAG,"ImplementThreads: Started");
Thread thread = new Thread(){
#Override
public void run() {
try{
sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
}
};
thread.start();
}
public void startConnection(BluetoothDevice device,UUID uuid){
Log.d(TAG,"StartConnection: Initializing connection");
myBluetoothService.startClient(device,uuid);
}
Thanks all for help, because It's very important for me !
Use this to interect with UI Thread for operations like updating textviews etc.
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
//YOUR CODE HERE
Message message = new Message();
message.obj = comingMsg;
mHandler2.sendMessage(message);
}
});
I've been looking for this for a while and everything I've tried has not worked yet. I implemented a Bluetooth connection service class that let's me connect and send messages via Bluetooth to a HC-05 module. I'm able to see each message within the console (with a Log), however, no matter what I tried, I can't seem to put the bytes received into my main activity where I can treat it. Here is the code I have in the BluetoothConnectionService class where my Log is located:
BluetoothConnectionService:
private Handler mHandler; // handler that gets info from Bluetooth service
// Defines several constants used when transmitting messages between the
// service and the UI.
private interface MessageConstants {
public static final int MESSAGE_READ = 0;
public static final int MESSAGE_WRITE = 1;
public static final int MESSAGE_TOAST = 2;
// ... (Add other message types here as needed.)
}
public void run(){
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
while (true) {
// Read from the InputStream
try {
bytes = mmInStream.read(buffer);
String incomingMessage = new String(buffer, 0, bytes);
Log.d(TAG, "InputStream: " + incomingMessage);
// Send the obtained bytes to the MainActivity
Handler mainActivityHandler = new Handler();
mainActivityHandler.obtainMessage(MessageConstants.MESSAGE_READ, bytes, -1, buffer);
// Send the obtained bytes to the UI activity.
/*Message readMsg = mHandler.obtainMessage(
MessageConstants.MESSAGE_READ, bytes, -1,
buffer);
readMsg.sendToTarget();*/
} catch (IOException e) {
Log.e(TAG, "write: Error reading Input Stream. " + e.getMessage() );
break;
}
}
}
MainActivity: (in the onCreate)
btnReadGlucose.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//On va envoyer quelle personne il faut lire le data
String patientName = mSpinner.getSelectedItem().toString();
int patientPosition = mSpinner.getSelectedItemPosition();
Log.d(TAG, "Le patient " + patientName + " a la position " + patientPosition + " est selectionne");
//Trouver quelle lettre envoyer
DataEnvoyer = mappingPatients(patientPosition);
RequestData = true;
//Envoi du data
envoyerCommandeBluetooth(DataEnvoyer);
//How do I call my handler ?
}
});
I'm still a newbie with Bluetooth communication handlers. I think I'm close to the answer but I really don't know how to get the message in the byte and save it to a value in my main activity.
Can anyone help ?
Thanks,
luisarcher.
METHOD 1 : If this service running on the same thread as the activity then bind the service with activity.
//IN YOUR ACTIVITY
startService(new Intent(getApplicationContext(), BluetoothService.class));
bindService(new Intent(getApplicationContext(), BluetoothService.class), mServiceConnection, Context.BIND_AUTO_CREATE);
private ServiceConnection mServiceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
BluetoothService.BackgroundBinder backgroundBinder = (BluetoothService.BackgroundBinder) iBinder;
mBackgroundService = backgroundBinder.getBackgroundService();
startPinging();
}
#Override
public void onServiceDisconnected(ComponentName componentName) {
mBackgroundService = null;
}
};
//IN SERVICE
public class BluetoothBinder extends Binder {
public BluetoothService getBluetoothService() {
return BluetoothService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "Inside onBind");
return new BluetoothBinder();
}
Now that the service is binded you can declare a getter in service for incomingMessage so when you press the button in activity it returns you the message.
METHOD 2(VIA HANDLER):if you need an interface to communicate across processes you can create a Messenger. It handles communication on single thread.
I haven't done this but a good post about this can be found here.
METHOD 3(VIA LocalBroadCast): In your bluetooth service send a localBroadcast whenever you receive a message
//SERVICE
private void sendMessage(String incomingMessage) {
Intent intent = new Intent("UNIQUE_ACTION");
intent.putExtra("incomingMessage", incomingMessage);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
public void run(){
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
while (true) {
// Read from the InputStream
try {
bytes = mmInStream.read(buffer);
String incomingMessage = new String(buffer, 0, bytes);
Log.d(TAG, "InputStream: " + incomingMessage);
sendMessage(incomingMessage);
//ACTIVITY
#Override
public void onResume() {
super.onResume();
// This registers mMessageReceiver to receive messages.
LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,new IntentFilter("UNIQUE_ACTION"));
}
// Handling the received Intents for the "UNIQUE_ACTION" event
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Extract data included in the Intent
String incomingMessage = intent.getStringExtra()("incomingMessage");
Log.d(TAG, incomingMessage);
}
};
#Override
protected void onPause() {
// Unregister since the activity is not visible
LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
super.onPause();
}
Also, I would suggest looking at this link for communication between service and activity.
P.S:Have a look at this library for bluetooth communication.It does provide methods to get data from Bluetooth and I have personally tested that it works with HC-05 and also has examples.
Is this right way of doing? AM using this samples https://altbeacon.github.io/android-beacon-library/samples.html
public class App extends Application
implements BootstrapNotifier, BeaconConsumer, RangeNotifier {
private final String TAG = "Application ";
protected static final Region beaconRegion = new Region("2f234454-cf6d-4a0f-adf2-f4911ba9ffa6", null, null, null);
protected BeaconManager beaconManager = null;
private RegionBootstrap regionBootstrap;
private BackgroundPowerSaver backgroundPowerSaver;
protected static String sLog = "";
#Override
public void onCreate() {
super.onCreate();
logIt(TAG, beaconRegion.getId1()+"onCreate - In"+beaconRegion.getUniqueId());
beaconManager = org.altbeacon.beacon.BeaconManager.getInstanceForApplication(this);
beaconManager.getBeaconParsers().clear();
beaconManager.getBeaconParsers().add(new BeaconParser().
setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24"));//iBeacon (tutti)
//--- wake up the app when a beacon is seen
regionBootstrap = new RegionBootstrap(this, beaconRegion);
//--- activate power saver
backgroundPowerSaver = new BackgroundPowerSaver(this);
beaconManager.bind(this);
logIt(TAG, "onCreate - Out");
}
private void logIt (String TAG, String msg) {
sLog += TAG + msg + "\n";
Log.w(TAG, msg);
}
//-------------------------//
//--- BootstrapNotifier ---//
//-------------------------//
#Override
public void didDetermineStateForRegion(int state, Region region) {
String msg = "didDetermineStateForRegion ";
switch(state) {
case MonitorNotifier.INSIDE:
msg +="(INSIDE)";
break;
case MonitorNotifier.OUTSIDE:
msg +="(OUTSIDE)";
break;
default:
msg +="(state=" +state +")";
break;
}
logIt(TAG, msg);
}
#Override
public void didEnterRegion(Region arg0) {
logIt(TAG, "didEnterRegion - In");
try {
beaconManager.startRangingBeaconsInRegion(beaconRegion);
logIt(TAG,"dER - startRangingBeaconsInRegion OK");
} catch (RemoteException e) {
logIt(TAG, "dER - startRangingBeaconsInRegion Err " +e);
}
logIt(TAG, "didEnterRegion - Out");
}
#Override
public void didExitRegion(Region region) {
logIt(TAG, "didExitRegion - In");
try {
beaconManager.stopRangingBeaconsInRegion(beaconRegion);
logIt(TAG,"dXR - stopRangingBeaconsInRegion OK");
} catch (RemoteException e) {
logIt(TAG, "dXR - stopRangingBeaconsInRegion Err " +e);
}
logIt(TAG, "didExitRegion - Out");
}
//----------------------//
//--- BeaconConsumer ---//
//----------------------//
#Override
public void onBeaconServiceConnect() {
logIt(TAG, "onBeaconServiceConnect - In");
beaconManager.setRangeNotifier(this);
logIt(TAG, "onBeaconServiceConnect - Out");
}
//---------------------//
//--- RangeNotifier ---//
//---------------------//
#Override
public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
logIt(TAG, "didRangeBeaconsInRegion - " +beacons.size() +" beacons");
Toast.makeText(getApplicationContext(), beaconRegion.getId1()+" beacon detected "+beacons.size(),
Toast.LENGTH_SHORT).show();
for(Beacon beac: beacons)
{
System.out.println(beac.getId1()+"id 1"+TAG);
if(beac.getId1().equals("2f234454-cf6d-4a0f-adf2-f4911ba9ffa6")/send notification
}
}
}
So Basically that class, which extends application am listening to beacons around. Below, is how am turning my phone into a beacon.I am doing this inside an activity on click of button. So there is two phones with app downloaded on both, once he clicks on one app the button I want the other phone to detect it since i have implemented in extends application class.
the turn ur android to beacon code.
Beacon beacon = new Beacon.Builder()
.setId1("2f234454-cf6d-4a0f-adf2-f4911ba9ffa6") // UUID for beacon
.setId2("1") // Major for beacon
.setId3("5") // Minor for beacon
.setManufacturer(0x004C) // Radius Networks.0x0118 Change this for other beacon layouts//0x004C for iPhone
.setTxPower(-56) // Power in dB
.setDataFields(Arrays.asList(new Long[]{0l})) // Remove this for beacon layouts without d: fields
.build();
BeaconParser beaconParser = new BeaconParser()
.setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24");
beaconTransmitter = new BeaconTransmitter(MenuActivity.this, beaconParser);
beaconTransmitter.startAdvertising(beacon, new AdvertiseCallback() {
#Override
public void onStartFailure(int errorCode) {
Log.e("tag", "Advertisement start failed with code: " + errorCode);
}
#Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
Log.i("tag", "Advertisement start succeeded.");
Toast.makeText(MenuActivity.this, "advertisement start succeeded",
Toast.LENGTH_SHORT).show();
System.out.println("startedddddddddddd");
}
});
// beaconTransmitter.stopAdvertising();
}
catch(Exception o)
{
System.out.println("affda "+o.getMessage());
}
I am also havig an issue, that didenterregion and didRangeBeaconsInRegion are fired many times, so am sending many notification to the user multiple times. its not user friendly.
The didRangeBeaconsInRegion callback is supposed to get called many times -- that's how ranging works. It is called approximately once per second when the beacon is detected to tell you it is there and give you a distance estimate.
If you want to fire a notification only once for the first time you get a call to this method, then you can set a flag for this specific beacon.
Here is an example of some code you might use to do that:
// Add this to the top of your class
private HashMap<String,Boolean> mAlreadySentNotification = new HashMap<String,Boolean>();
...
// Add this to the inside of your didRangeBeaconsInRegion method
if (mAlreadySentNotification.get(beacon.toString())) {
mAlreadySentNotification.put(beacon.toString(), true);
// Send notification here.
}
I followed these instructions to integrate both Libgdx and native android code using ActionResolver interface. I have no problem calling the Android method from the Libgdx part of my code. But I am hitting a dead end when I am trying to intergrate Google IAP with Libgdx. According to TrivialDrive example, it uses mPurchaseFinishedListener (outside of calling method).
My question is: how do I pass this IAP resultcode back to Libgdx since the listener is outside the calling method? Currently, purchase process went through, but the libgdx part of my code is not being "informed" of the purchase status/result.
This is my code:
Any help is much appreciated.
ActionResolver:
public interface IActionResolver {
public int requestIabPurchase(int product);
}
MainActivity:
public class MainActivity extends AndroidApplication implements IActionResolver {
// Debug tag, for logging
static final String TAG = "greatgame";
// Does the user have the premium upgrade?
boolean mIsUpgraded = false;
// SKUs for our products: the cat, all, or pow
static final String SKU_UPGRADE = "android.test.purchased";
// (arbitrary) request code for the purchase flow
static final int RC_REQUEST = 10001;
// The helper object
IabHelper mHelper;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
cfg.useGL20 = false;
initialize(new Catland(this), cfg);
}
void iAbStartup() {
String base64EncodedPublicKey = "some key";
// Create the helper, passing it our context and the public key to verify signatures with
Log.d(TAG, "Creating IAB helper.");
mHelper = new IabHelper(this, base64EncodedPublicKey);
// enable debug logging (for a production application, you should set this to false).
mHelper.enableDebugLogging(true);
// Start setup. This is asynchronous and the specified listener
// will be called once setup completes.
Log.d(TAG, "Starting setup.");
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
Log.d(TAG, "Setup finished.");
if (!result.isSuccess()) {
// Oh noes, there was a problem.
Log.d(TAG, "Problem setting up in-app billing: " + result);
return;
}
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) {
return;
}
// IAB is fully set up. Now, let's get an inventory of stuff we own.
Log.d(TAG, "Setup successful. Querying inventory.");
mHelper.queryInventoryAsync(mGotInventoryListener);
}
});
}
// Listener that's called when we finish querying the items and subscriptions we own
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
Log.d(TAG, "Query inventory finished.");
// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) {
return;
}
// Is it a failure?
if (result.isFailure()) {
Log.d(TAG, "Failed to query inventory: " + result);
return;
}
Log.d(TAG, "Query inventory was successful.");
// Do we have the SKU_UPGRADE upgrade?
Purchase thisUpgrade = inventory.getPurchase(SKU_UPGRADE);
mIsUpgraded = (thisUpgrade != null && verifyDeveloperPayload(thisUpgrade));
Log.d(TAG, "User is " + (mIsUpgraded ? "Upgraded" : "Free"));
Log.d(TAG, "Initial inventory query finished; enabling main UI.");
runPurchaseFlow(submitProduct);
}
};
// Run real purchase flow
public void runPurchaseFlow(int product) {
Log.d(TAG, "runPurchaseFlow");
/* TODO: for security, generate your payload here for verification. See the comments on
* verifyDeveloperPayload() for more info. Since this is a SAMPLE, we just use
* an empty string, but on a production app you should carefully generate this. */
String payload = "";
if (product == 1)
mHelper.launchPurchaseFlow(this, SKU_UPGRADE, RC_REQUEST, mPurchaseFinishedListener, payload);
}
// Callback for when a purchase is finished
IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
Log.d(TAG, "Purchase finished: " + result + ", purchase: " + purchase);
// if we were disposed of in the meantime, quit.
if (mHelper == null) return;
if (result.isFailure()) {
Log.d(TAG, "Error purchasing: " + result);
return;
}
if (!verifyDeveloperPayload(purchase)) {
Log.d(TAG, "Error purchasing. Authenticity verification failed.");
return;
}
Log.d(TAG, "Purchase successful.");
if (purchase.getSku().equals(SKU_CAT)) {
// bought the upgrade!
Log.d(TAG, "Purchase Upgrade. Congratulating user.");
mIsUpgraded = true;
// how do i pass this result to the libgdx?
}
}
};
/** Verifies the developer payload of a purchase. */
boolean verifyDeveloperPayload(Purchase p) {
String payload = p.getDeveloperPayload();
return true;
}
#Override
public int requestIabPurchase(int product) {
iAbStartup();
return 0; // how do i get the result from mPurchaseFinishedListener?
}
}
PurchaseScreen
result = greatgame.actionResolver.requestIabPurchase(1);
You won't be able to return the result from requestIabPurchase() - the only methods of doing so would block for a long time. The best way, in my opinion, would be to create a listener interface of your own that your LibGdx project implements, and pass that into your request interface. For example:
In your libGdx project somewhere:
interface PurchaseCallback {
public int setPurchaseResult(int result);
}
ActionResolver:
public interface IActionResolver {
public int requestIabPurchase(int product, PurchaseCallback callback);
}
In PurchaseScreen, implement PurchaseCallback:
#override
public int setPurchaseResult(int result) {
// Yay! I have a result from a purchase! Maybe you want a boolean instead of an int? I don't know. Maybe an int (for the product code) and a boolean.
}
...and pass whatever is implementing PurchaseCallback (I'm assuming your PurchaseScreen does itself):
result = greatgame.actionResolver.requestIabPurchase(1, this);
Finally, hook it all up in MainActivity:
PurchaseCallback mCallback = null;
mPurchaseFinishedListener = ... etc. etc.
.
.
.
if (mCallback != null) {
mCallback.setPurchaseResult(0);
}
.
.
.
#Override
public int requestIabPurchase(int product, PurchaseCallback callback) {
mCallback = callback; // save this for later
iAbStartup();
return 0;
}
Note that you should call PurchaseCallback.setPurchaseResult() everywhere that mPurchaseFinishedListener has return, not only at the line // how do i pass this result to the libgdx? - otherwise, you will never know if a purchase failed or is just taking a really long time.