Flutter and native android method in alarm manager callback function - java

I am having an issue calling background function which is written for android specific. My use case - press on the button, schedule the SMS (with alarm manager), call callback function, and send SMS.
I'm feeling stuck a bit so I hope someone can show me what can I do to make it right.
Also if I put await platform.invokeMethod('sendsms') on button press, the method fires. So I am doing something wrong when trying to fire the method in the backend.
Here is also a gist with all files: https://gist.github.com/benzo11/901c1885fff46e37d954a139dbbf0470
Thank you in advance!
// In the widget state class
Future<void> _sendSms() async {
await AndroidAlarmManager.oneShotAt(
DateTime.now().add(const Duration(seconds: 1)),
1,
printHello, // callback function which triggers
alarmClock: true,
);
}
// Top level
void printHello() async {
final DateTime now = DateTime.now();
final int isolateId = Isolate.current.hashCode;
print("[$now] Hello, world! isolate=${isolateId} function='$printHello'");
// Method undefined in isolate
var result = await platform.invokeMethod('sendsms').then((res) {
print(res);
});
}
public class MainActivity extends FlutterActivity {
public static final String TAG = "AlarmExampleMainActivity";
private static final String CHANNEL = "samples.flutter.dev/battery";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndroidAlarmManagerPlugin
.registerWith(registrarFor("io.flutter.plugins.androidalarmmanager.AndroidAlarmManagerPlugin"));
}
#Override
public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler((call, result) -> {
// Note: this method is invoked on the main thread.
if (call.method.equals("sendsms")) {
String rez = sendSMS();
result.success(rez);
} else {
result.notImplemented();
}
});
}
private String sendSMS() {
String phoneNo = "009981111";
String msg = "Message body";
SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage(phoneNo, null, msg, null, null);
return "Sent";
}
}

Related

Xamarin Android - Splash Screen Doesn't Work On Resume

I followed this article and pieced together information with other articles to create a splash screen:
https://learn.microsoft.com/en-us/xamarin/android/user-interface/splash-screen
The splash screen works well when I start the app up by tapping on the app's icon. However, if the app is already running and I switch to it, the screen goes white for a few seconds while the app resumes. Why?
Here is my code:
[Activity(Label = "Hardfolio", Icon = "#drawable/icon", Theme = "#style/MyTheme.Splash", MainLauncher = true, NoHistory = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class SplashActivity : FormsAppCompatActivity
{
static readonly string TAG = "Hardfolio: " + typeof(SplashActivity).Name;
public override void OnCreate(Bundle savedInstanceState, PersistableBundle persistentState)
{
base.OnCreate(savedInstanceState, persistentState);
}
// Launches the startup task
protected override void OnResume()
{
base.OnResume();
var startupWork = new Task(AppStartup);
startupWork.Start();
}
// Simulates background work that happens behind the splash screen
async void AppStartup()
{
StartActivity(new Intent(Application.Context, typeof(MainActivity)));
}
}
[Activity(Label = "Hardfolio", Icon = "#drawable/icon", Theme = "#style/MainTheme", MainLauncher = false, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
[IntentFilter(new[] { UsbManager.ActionUsbDeviceAttached })]
[MetaData(UsbManager.ActionUsbDeviceAttached, Resource = "#xml/device_filter")]
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
#region Fields
private AndroidHidDevice _TrezorHidDevice;
private UsbDeviceAttachedReceiver _UsbDeviceAttachedReceiver;
private UsbDeviceDetachedReceiver _UsbDeviceDetachedReceiver;
private object _ReceiverLock = new object();
#endregion
#region Overrides
protected override void OnCreate(Bundle bundle)
{
try
{
_TrezorHidDevice = new AndroidHidDevice(GetSystemService(UsbService) as UsbManager, ApplicationContext, 3000, 64, TrezorManager.TrezorVendorId, TrezorManager.TrezorProductId);
ServicePointManager.ServerCertificateValidationCallback += (o, certificate, chain, errors) => true;
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
Xamarin.Forms.Forms.Init(this, bundle);
RegisterReceiver();
var application = new App(new CrossPlatformUtilities(new IsolatedStoragePersister(), new AndroidRESTClientFactory()), _TrezorHidDevice, GetPin);
LoadApplication(application);
}
catch (Exception ex)
{
Logger.Log("Android crash", ex, nameof(Wallet.Droid));
Toast.MakeText(ApplicationContext, ex.ToString(), ToastLength.Long).Show();
}
}
private async Task<string> GetPin()
{
var taskCompletionSource = new TaskCompletionSource<string>();
RunOnUiThread(async () =>
{
var pin = await TrezorPinPad.GetPin();
taskCompletionSource.SetResult(pin);
});
return await taskCompletionSource.Task;
}
protected override void OnResume()
{
base.OnResume();
Logger.Log($"Resuming... Setting up Trezor listeners. _TrezorHidDevice is {(_TrezorHidDevice == null ? "null" : "not null")}", null, nameof(Wallet.Droid));
RegisterReceiver();
}
private void RegisterReceiver()
{
try
{
lock (_ReceiverLock)
{
if (_UsbDeviceAttachedReceiver != null)
{
UnregisterReceiver(_UsbDeviceAttachedReceiver);
_UsbDeviceAttachedReceiver.Dispose();
}
_UsbDeviceAttachedReceiver = new UsbDeviceAttachedReceiver(_TrezorHidDevice);
RegisterReceiver(_UsbDeviceAttachedReceiver, new IntentFilter(UsbManager.ActionUsbDeviceAttached));
if (_UsbDeviceDetachedReceiver != null)
{
UnregisterReceiver(_UsbDeviceDetachedReceiver);
_UsbDeviceDetachedReceiver.Dispose();
}
_UsbDeviceDetachedReceiver = new UsbDeviceDetachedReceiver(_TrezorHidDevice);
RegisterReceiver(_UsbDeviceDetachedReceiver, new IntentFilter(UsbManager.ActionUsbDeviceDetached));
}
}
catch (Exception ex)
{
Logger.Log($"Error registering Hid receivers", ex, nameof(Wallet.Droid));
}
}
#endregion
}
If you start your application, from within another application/or using the ClearTask flag/or if your app is performing a cold start (has been closed in the background), and perhaps i other ways as well, you will see a "Preview" screen, which is the background of your current theme (kind of what you are already doing for your SplashScreen, which shows the theme background)...
But if your "#style/MainTheme" has a simple white background, this will be what you might see when reentering your app.
Therefore you can consider using the "SetTheme" method in OnCreate. There is more about this in this link:
https://developer.android.com/topic/performance/vitals/launch-time
Hope it helps.

UtteranceProgressListener won't call the functions

I am trying to make a speech powered app, however I have run into a major problem.
My UtteranceProgressListener Class will not call any of the given methods regardless of where I place the Speak method.
Here is my code:
This is my OnCreate Method:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
voiceBtn = (Button) findViewById(R.id.startListeningBtn);
voiceBtn.setEnabled(false);
textToSpeech = new TextToSpeech(mContext,new botListener());
}
This is the OnInitListner Imeplementation
public class botListener implements TextToSpeech.OnInitListener{
#Override
public void onInit(int i) {
if(i == TextToSpeech.SUCCESS)
{
int s = textToSpeech.setOnUtteranceProgressListener(new UtteranceProgressListener() {
#Override
public void onStart(String s) {
Toast.makeText(getApplicationContext(),"Done Speaking",Toast.LENGTH_SHORT).show();
}
#Override
public void onDone(String s) {
Toast.makeText(getApplicationContext(),s,Toast.LENGTH_SHORT).show();
}
#Override
public void onError(String s) {
Toast.makeText(getApplicationContext(),"Done Speaking",Toast.LENGTH_SHORT).show();
}
});
Log.d(TAG,String.valueOf(s));
int result = textToSpeech.setLanguage(Locale.ENGLISH);
if(result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED){
Log.e(TAG,"Language not supported");
Intent installLanguage = new Intent(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
startActivity(installLanguage);
}
Log.d(TAG,"Started Voice Speaker");
}
else{
Log.e(TAG,"initialization failed");
}
}
}
Now, when I press the button, the event that fires is:
public void initVoiceRecog(View v){
//Toast.makeText(mContext,"Clicked",Toast.LENGTH_SHORT).show();
Speak("hello","1");
// does some other things here after that
}
private void Speak(String text,String identifierID){
if(Build.VERSION.SDK_INT>21) {
Bundle params = new Bundle();
params.putString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,identifierID);
textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, params, identifierID);
}
else{
// ttsMap is a HashMap
ttsMap.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID,identifierID);
textToSpeech.speak(text,TextToSpeech.QUEUE_FLUSH,ttsMap );
}
}
My Question is, after saying hello, it does not fire the OnStart() or the OnError() or the OnDone() methods. Why is this happening?
I tried with the deprecated setOnUtteranceListner() as well, same result. It does not fire any of the methods, the Toasts don't show up.
Please tell a fix or a workaround for this.
The Devices I tried on are:
API 19 Micromax Canvas Nitro
API 21 Samsung S4
API 23(Marshmellow) ASUS Zenfone
I finally figured out why the callbacks were not working. Turns out, they were working and calling on a separate thread. So to execute the normal functionality, call the functions in the 'Activity.this.RunOnUiThread' and put this in the call back fuctions.

Where to put in Android code to communicate with server over http

The question is how to communicate with an Android phone to a server, so that if the Activity is left and the call in the Activity was not successful to repeat the transaction once again automatically. Just now I use the AsyncTask of Android to communicate with the server:
new AsyncTask<String, Void, List<String>>() {
#Override
protected void onPreExecute(
showWaitDialog();
}
#Override
protected void onPostExecute(List<String> msgList) {
//here I put the handling after the POST ie. error and success handling
hideWaitDialog();
if (msgList.isEmpty() {
//success handling --> starting an new Activity
} else {
errorView.setText (...);
errorLayout.setVisibility (View.VISIBLE);
}
}
#Override
protected List<String> doInBackground(String... params) {
List<String> msgs = new ArrayList<String>();
try{
//for example submitting an JSONObject
JSONObject result = HttpUtils.sendHttpPost(
AppConstants.WEB_URL, jsonObject);
//error handling on the result
boolean hasErrors = JsonResult.isOk(result);
if (hasErrors) {
// adding errors to msgs list
String[] errorMessages = JsonResult.getErrorMessages (result,...);
fillList (msgs, errorMessages);
return msgs;
}
} catch (CommunicationError er) {
msgs.add (er...);
}
return msgs;
}
}
The problem with this approach is, that if I don't have a successful transmission of the data I must stay in the same Activity. Until now I show an error message to the user and he is in charge to submit by a button again the results to the server.
What I'm looking for is some Activity that remains persistent in the memory which runs later in the case that the transmission wasn't made.
As an application case I use this to dynamically upload pictures for a Waypoint in a map if I pressed that waypoint. In some case it can happens that the connection to the mobile service provider isn't available (mountains, forest, far apart from antenna). Then I want to leave the map Activity and switch to the detail view of this waypoint. In the success case I put the picture into my model classes and make an serialization. If the user clicks again on the same waypoint the picture is not loaded again. In the non success case I don't want to wait that the user clicks against on the waypoint to retrieve the image. In fact I need a background task, some sort of a queue that pictures of waypoints that are already visited on couldn't be retrieved are loaded until the communication part gives back a positive result and the image can be written into the model. The next time the user is pressing the Waypoint the picture will be then present.
Are there any best practices for making such a code implementation?
Is there any example around?
Is there a better way of doing this?
Yes, you need to Implement Intent Service for this requirement
According to the developers website
The IntentService class provides a straightforward structure for running an operation on a single background thread.
For complete details and working source code, Go through the Android Docs
Thanks to the answer of David.
I just read after the suggestion the tutorial at
[1] http://code.tutsplus.com/tutorials/android-fundamentals-intentservice-basics--mobile-6183
After my tests I prefered a Service (not an IntentService)
and created a service: SubmissionService
public class SubmissionIntentService extends Service {
private List<PendingMessage> pMsgList = new CopyOnWriteArrayList<PendingMessage>();
private Handler handler = new Handler();
private boolean hasAppStopped = false;
private Runnable runner;
public SubmissionIntentService() {
super();
Log.d (TAG, "Service created...");
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
PendingMessage pMessage = (PendingMessage) intent.getParcelableExtra(AppConstants.MESSAGE_OBJECT);
synchronized (pMsgList) {
pMsgList.add(pMessage);
}
if (runner == null) {
handler.postDelayed(runner = initializeRunnable(), 500);
}
return Service.START_NOT_STICKY;
}
private void runAsLongAppIsActive (Runnable runner) {
if (!hasAppStopped) {
handler.postDelayed (runner, SOME_INTERVAL_CONSTANT);
}
}
private Runnable initializeRunnable() {
Runnable result;
result = new Runnable() {
#Override
public void run() {
if (pMsgList.isEmpty()) {
runAsLongAppIsActive (this);
return;
}
PendingMessage[] pMArray = null;
synchronized(pMsgList) {
pMArray = pMsgList.toArray (new PendingMessage[pMsgList.size()]);
}
if (pMArray==null || pMArray.length==0) {
runAsLongAppIsActive (this);
return;
}
Log.d (TAG, "Message List size is actually :"+pMArray.length);
for (PendingMessage pM: pMArray) {
try {
JSONObject jsonMess = JSONSendMessage.buildOutput (pM);
JSONObject result = HttupUtils.sendHttpPost (WEB_URL, jsonMess);
boolean hasErrors = JSONResult.isOk (result);
if (hasErrors) {
//TODO: error handling in case of transmission
//don't remove the message from the queue
runAsLongAppIsActive(this);
return;
}
//remove pending transmission of the queue if success
synchronized (pMsgList) {
pMsgList.remove (pM);
}
//inform over receiver if activity is shown
Intent broadcastIntent = new Intent();
//put data in intent
sendBroadcast (intent);
//more important
WayPointModel model = ModelInstance.getWayPointModel();
model.addToModel (pM, result);
model.store();
} catch (Exception e) {
continue; //try to send other messages
}
}
runAsLongAppIsActive (this);
}
};
return result;
}
}
#Override
public void onDestroy() {
hasAppStopped = true;
handler.removeCallbacks (runner);
super.onDestroy();
}
}
Further I added a ResponseReceiver:
public class ResponseReceiver extends BroadcastReceiver {
public static final String ACTION_RESP = "MESSAGE_PROCESSED";
#Override
public void onReceive(Context context, Intent intent) {
//work in progress...
}
}
and in the Activity where I want to be informed about events:
public class SomeActivity extends Activity {
private ResponseReceiver receiver;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
IntentFilter filter = new IntentFilter(ResponseReceiver.ACTION_RESP);
filter.addCategory(Intent.CATEGORY_DEFAULT);
receiver = new ResponseReceiver();
registerReceiver(receiver, filter);
...
}
}
and finally to send messages over Http:
Intent msgIntent = new Intent(this, SubmissionIntentService.class);
msgIntent.putExtra(...);
startService(msgIntent);
don't forget to declare the service in your manifest:
<service android:name="ch.xxx.app.service.SubmissionIntentService" />
Observations:
- I called the method startService(...) from different Activities. The constructor is only called once.
==> I have just on instance of the service for all Activities (exactly what I need).
What I don't get until now:
- Putting back data to the Activity. What is if the Activity is at the moment no shown?

AsyncTask handle message recieved by another class and update progress

My problem is this;
I have a AsyncTask that works fine, and on doInBackground() it calls a new class that sync my data to a web service using REST service, i don't have everything on a unique class because i need the same content sync for different activitys and it's easier this way.
What i need is, on the sync procedure, i can get the number of "contacts" and everytime it downloads a contact, removes 1 from the "contacts" lenght, so, i nedd to show on the progress dialog the length of contact and refresh everytime it downloads a new "contact"
hre's my code for the AsyncTask:
public class syncContentTask extends AsyncTask<String, String, Boolean> {
private ProgressDialog mprogress;
private Context context;
//token for JSON header to authenticate
String authToken;
public syncContentTask(Context cxt, String token) {
this.context = cxt;
mprogress = new ProgressDialog(context);
authToken = token;
}
protected void onPreExecute() {
mprogress = ProgressDialog.show(context, "Sync", "Sync in progress...");
}
#Override
protected Boolean doInBackground(String... params) {
syncData syncData = new syncData();
syncData.syncData(context, authToken);
publishProgress(progress);
return true;
}
protected void onProgressUpdate(String... progress) {
//mprogress.setProgress(Integer.parseInt(progress[0]));
}
protected void onPostExecute(Boolean result) {
if (result) {
mprogress.dismiss();
}
}
}
In the Sync Data class i have functions that handles the HttpRequest and database stuff...
can anyone help??
You need to create a listener for your data progress and have it update the progress bar. Right now it looks like this line:
syncData.syncData(context, authToken);
blocks and no updates are provided to your progress indicator until it is done. So, you need something like:
MyListener listener = new MyListener(context);
SyncData syncData = new syncData(listener);
And in your listener have callback methods like myListener.downloadStarted() , myListener.updateProgressBar(int progress) and myListener.downloadCompleted() that your syncData class calls as the download progresses.
For example:
public abstract class SDScanAdapter implements SDScanListener {
public void startScan() {
}
public void updateScanProgress(int scanItemsTotal, int scanItemsCompleted) {
}
public void scanComplete() {
}
}
Then create a listener class:
public class ScanListener extends SDScanAdapter {
#Override
public void scanComplete(String contactName, String action) {
runOnUiThread(scanComplete);
}
#Override
public void startScan() {
runOnUiThread(startScan);
}
#Override
public void updateScanProgress(int scanItemsTotal,
int scanItemsCompleted) {
if (scanCountTotal != scanItemsTotal) {
scanCountTotal = scanItemsTotal;
progressBar.setMax(scanCountTotal);
}
if (scanCountUpdate != scanItemsCompleted) {
scanCountUpdate = scanItemsCompleted;
runOnUiThread(updateScanProgress);
}
}
}
And then for this example you need Runnables (startScan, scanComplete and updateScanProgress) that perform UI tasks, like updating the progress bar. In your case, you may also want to load some of the results, etc.
Then in your AsyncTask you do:
ScanListener listener = new ScanListener();
SyncData syncData = new syncData(listener);
Assuming the SDScanListener class and AsyncTask are all in your Activity. Also, your SyncData calss will need to have a SDScanListener variable that is set when it instantiates. Then, while it does its job, calls are made to the listener methods like:
scanListener.startScan();
And while it progresses, it calls the other methods (and corresponding parameters are passed in).

How to wait for a function in the doBackground to return in android?

I have a asyc task that stores objects in cloudmine. Now I want to wait for the confirmation from cloudmine that the objects are stored. But before I get the conversation the postExecute method starts, which gives the wrong toast message. Main problem is that the cloudmine task itself is async and so I want doInBacground to wait for the cloudmine async to finish. Nothing I have tried has worked.
Here is the code:
public class createCloudmineLot extends AsyncTask<Void,Void,Boolean>
{
private boolean success;
private boolean inputFault;
protected void onPostExecute(Boolean arg0)
{
System.out.println("postExecute sucess value: " + success);
if(inputFault)
{
t = Toast.makeText(AddParking.this, "No Input!Please press enter after each input",Toast.LENGTH_LONG);
t.show();
}
else
if(arg0)
{
t = Toast.makeText(AddParking.this, "Parking Lot was added:" + success,Toast.LENGTH_LONG);
t.show();
}
else
{
t = Toast.makeText(AddParking.this, "Unable to add Parking Lot" + success,Toast.LENGTH_LONG);
t.show();
}
return;
}
#Override
protected Boolean doInBackground(Void... params)
{
// TODO Auto-generated method stub
CMApiCredentials.initialize(APP_ID, API_KEY, getApplicationContext());//initializing Cloudmine connetion
SimpleCMObject parkingLot = new SimpleCMObject();
if(name.equals("") | address.equals("") | pricing.equals("") | hours.equals("") | latitude.equals("") | longitude.equals(""))
{
inputFault = true;
return null;
}
parkingLot.add("name", name);
parkingLot.add("address",address);
parkingLot.add("pricing",pricing);
parkingLot.add("hours",hours);
double lat = Double.parseDouble(latitude);
double lon = Double.parseDouble(longitude);
CMGeoPoint locationCoordinates = new CMGeoPoint(lat,lon);
parkingLot.add("location",locationCoordinates);
parkingLot.save(new ObjectModificationResponseCallback()
{
public void onCompletion(ObjectModificationResponse response)
{
success = response.wasSuccess();
System.out.println("doInbackground: "+ success);
}
});
return success;
}//end of doBackground
}//end on AsyncTask
This is when I try with Broadcast Receivers. Using the receiver, nothing happens.
public class EditParking extends Activity
{
private ListView lotList;
private ArrayList<String> lots;
private ArrayAdapter<String> arrayAdpt;
private class ReceiveBroadcastActivity extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
// TODO Auto-generated method stub
System.out.println("received");
ArrayList<String> lots = intent.getStringArrayListExtra("ParkingLots");
ListView lotlist = (ListView)findViewById(R.id.lotList);
ArrayAdapter<String> arrayAdpt = new ArrayAdapter<String>(EditParking.this,android.R.layout.simple_list_item_1,lots);
lotList.setAdapter(arrayAdpt);
System.out.println("lots: "+ lots);
}
};
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.editparking_layout);
lotList = (ListView)findViewById(R.id.lotList);
lots = new ArrayList<String>();
lots.add("hello");
arrayAdpt = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,lots);
lotList.setAdapter(arrayAdpt);
arrayAdpt.setNotifyOnChange(true);
CMApiCredentials.initialize(APP_ID, API_KEY, getApplicationContext());//initializing Cloudmine connection
CMStore store = CMStore.getStore();
store.loadAllApplicationObjects(new CMObjectResponseCallback()
{
public void onCompletion(CMObjectResponse response)
{
for(CMObject object : response.getObjects())
{
// do something with each object
SimpleCMObject lot = (SimpleCMObject)object;
String lotInfo = lot.getString("name") + "\n" + lot.getString("address") +
"\nPrice: " + lot.getString("pricing") + "\nHours: " + lot.getString("hours");
System.out.println(lotInfo);
}
Intent broadcastIntent = new Intent();
lots.add("world");
broadcastIntent.putStringArrayListExtra("ParkingLots", lots);
broadcastIntent.setAction("com.cs275.findparking.broadcast");
sendBroadcast(broadcastIntent);
}
The problem is that the call you are doing inside the doInBackground is also an asynchronous call, you need to realize that by the time the execution thread reaches the end of doInBackground and returns an object, the onPostExecute will be called, so basically the problem in your case is that since your call is also asynchronous, its not blocking the thread and it moves inmediately to the end of the doInBackground method and the "callback" you receive from that call is executed way later than the whole asynctask did it's job, so, that said there's different ways to approach the issue.
1.- You could force the worker thread in doInBackground to wait for your callback to be executed(this requires a perfect understanding of java threads and the wait/notify methods)
2.- A simple approach would be, not to rely on onPostExecute method, and in stead send a Broadcast Receiver message from the callback method of your call, which will be handled in your activity whenever is done..
Regards!

Categories