I have two separate applications written using Xamarin.Android; for the sake of discussion, let's call them "Tristan" and "Isolde". Tristan has some state information that Isolde sometimes needs to know. Complication: Tristan may or may not be running at the moment Isolde develops the need to know his state.
I've got kludge working now where Isolde sends a special launch intent to Tristan, who then uses a broadcast intent to send information back to Isolde. (See my earlier question for details.)
"But wait!" I hear you cry, "Is this not a perfect use case for StartActivityForResult()?" Indeed it is! The code is a whole lot simpler, and everything I've read implies that this is how Android wants you to do stuff like this.
Unfortunately, I can't get it to work (despite trying many variations and reading the dozen-or-so related questions on this very site). My specific problem is that in Isolde's OnActivityResult() callback, the resultCode is always Result.Canceled and the data is always null.
Here is the code for Tristan (where commented-out bits represent variations I've tried):
using Android.App;
using Android.Content;
namespace com.example.Tristan.Android
{
[Activity(Name ="com.example.Tristan.Android.IsoldeQueryActivity")]
public class IsoldeQueryActivity : Activity
{
protected override void OnStart()
{
// base.OnStart();
var rtn = new Intent();
rtn.PutExtra("Test", "test");
//rtn.SetAction("TestAction");
SetResult(Result.Ok, rtn);
Finish();
//FinishActivity(1234);
}
}
}
And here is the relevant code from the Activity where Isolde needs to ask for Tristan's state:
private TaskCompletionSource<bool> TristanStateCompletion;
public async Task GetTristanState()
{
TristanStateCompletion = new TaskCompletionSource<bool>();
var req = new Intent("com.example.Tristan.Android.IsoldeQueryActivity");
//req.PutExtra(Intent.ExtraReturnResult, true);
StartActivityForResult(req, 1234);
var rtn = await TristanStateCompletion.Task;
if (!rtn) bomb("can't get state");
TristanStateCompletion = null;
}
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if(requestCode == 1234) {
DoStuffWith(data);
TristanStateCompletion?.TrySetResult(true);
}
}
Diagnostics -- or rather, a specific lack of them -- leads me to believe that Tristan's IsoldeQueryActivity.OnStart() is never actually being called.
Ideas, requests for additional information and/or useful experiments to try are all welcome. (If your idea is "Put <thing> in the manifest", remember this is Xamarin.Android and I have to do that by putting <relatedThing> in the attribute decorating the Activity.)
Edited to add: In Isolde's code, DoStuffWith(data) was crashing because data was null. When I changed that method to avoid that, I found that I got a (slightly later) exception thrown in StartActivityForResult():
Android.Content.ActivityNotFoundException No Activity found to handle Intent { act=com.example.Tristan.Android.IsoldeQueryActivity }
This leads me to believe I'm not creating the Intent properly in Isolde. Do I need to be using one of the other Intent constructors? If so, how specifically?
Okay, I think I have this figured out. The code in my original question had three major problems:
I was building the Intent incorrectly in Isolde.
I didn't export the IsoldeQueryActivity in Tristan.
The call to base.OnStart() in Tristan's OnStart override is mandatory.
Here is the working version of Tristan:
using Android.App;
using Android.Content;
namespace com.example.Tristan.Android
{
[Activity(Name ="com.example.Tristan.Android.IsoldeQueryActivity", Exported=true)]
public class IsoldeQueryActivity : Activity
{
protected override void OnStart()
{
base.OnStart();
var rtn = new Intent();
rtn.PutExtra("Test", "test");
SetResult(Result.Ok, rtn);
Finish();
}
}
}
And here is the fixed code from Isolde:
private TaskCompletionSource<bool> TristanStateCompletion;
public async Task GetTristanState()
{
TristanStateCompletion = new TaskCompletionSource<bool>();
var req = new Intent();
req.SetComponent(new ComponentName("com.example.Tristan.Android", "com.example.Tristan.Android.IsoldeQueryActivity"));
StartActivityForResult(req, 1234);
var rtn = await TristanStateCompletion.Task;
if (!rtn) bomb("can't get state");
TristanStateCompletion = null;
}
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if(requestCode == 1234) {
if(resultCode != Result.Ok) bomb("bad resultCode {0}", resultCode);
if(data == null) bomb("null data from Tristan");
DoStuffWith(data);
TristanStateCompletion?.TrySetResult(true);
}
}
Related
I have a Flutter project that requires some certain features that needs to be implemented in native Android Activity or iOS ViewController. is there a way to navigate to android Activity and pass data to it and also retrieve data from it in Flutter?
and if it's impossible, is it possible to show an Activity or fragment from Android, and a ViewController from iOS, as a Widget in Flutter?
Not sure whether this is the best way and I only created it for Android, but this is what I did.
Simple Flutter method channel calling native:
static const platform = const MethodChannel(MY_CHANNEL);
string result await platform.invokeMethod("mycall");
From the native Android part in your mainActivity:
//Class attribute
private Result myresult;
//Method chanel
new MethodChannel(getFlutterView(), MY_CHANNEL).setMethodCallHandler(
(call, result) -> {
// Note: this method is invoked on the main thread.
if (call.method.equals("mycall")) {
myresult = result; //Store the flutter result
Intent intent1 = new Intent(MyClass.class);//Start your special native stuff
startActivityForResult(intent1, RQ_CODE);
} else {
result.notImplemented();
}
});
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Check which request we're responding to
if (requestCode == RQ_CODE) {
myresult.success("this will be your result"); //Probably do something with the data instead of a static string.
}
}
Basically the same can be done for iOS
use intent or android_intent packages for android and for ios use https://flutter.dev/docs/get-started/flutter-for/ios-devs link
I am using a Fragment to start a new Activity using startActivityForResult(), I am getting the result (Bundle) in onActivityResult() method.Since onActivityResult() called before onResume().I want to make sure, I keep/save the Bundle properly so that when Fragment's onResume() gets called, I get the kept/saved result to perform further action.
What are the different ways to achieve this. I tried using getArguments()/setArguments(), but that seems to be not the right way to achieve this.
Try this
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == MY_REQUEST) {
// Make sure the request was successful
if (resultCode == RESULT_OK) {
mResultBundle = data.getExtras(); //mResultBundle is in fragment
//scope
}
}
}
#Override
protected void onResume(){
super.onResume();
if(mResultBundle != null){
// process saved bundle from activity result here
// don't forget to set it back to null once you are done
mResultBundle = null;
}
}
I am trying to integrate BrainTree into my app, but I'm having some issues with startActivityForResult() and onActivityResult(). I have already properly retrieved a clientToken but for some reason I'm always receiving a resultCode of 2 in onActivityResult. In addition I also get this in my Logs:
I/art: Rejecting re-init on previously-failed class
java.lang.Class
Any idea what's going on? Here's some snippets of my code in case it helps:
This is the onClick method in my fragment.
void onPaymentClick() {
PaymentRequest paymentRequest = new PaymentRequest().clientToken("<client_token>");
getActivity().startActivityForResult(paymentRequest.getIntent(storeOrderActivity), 1);
}
}
And this is the onActivityResult code in my activity. (PostOrder is the the function that's supposed to run once Braintree sends a nonce back to me, but this doesn't even run since I get a resultCode of 2)
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1) {
if (resultCode == Activity.RESULT_OK) {
PaymentMethodNonce paymentMethodNonce = data.getParcelableExtra(
BraintreePaymentActivity.EXTRA_PAYMENT_METHOD_NONCE
);
PostOrder.postOrder(this, fabCartAdapter.getCart(), paymentMethodNonce.getNonce());
}
}
}
The resultCode==2 is reserved for BRAINTREE_RESULT_DEVELOPER_ERROR:
The payment method flow halted due to a resolvable error (authentication, authorization, SDK upgrade required). The reason for the error will be returned in a future release.
Probably you need to double check if the SDK is set it up correctly.
I wanna write a native module for to scan barcode but it too hard for me to figure out a way to handle the result without adding a method to the MainActivity. It's not a good idea to modify the MainActivity such heavily because it's no easy job for application developers who writes javascript to use the module.
For example, if I use ZXing Android Embedded: https://github.com/journeyapps/zxing-android-embedded, I have to add a method to MainActivity to handle the result.
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
if(result != null) {
if(result.getContents() == null) {
Toast.makeText(this, "Cancelled", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "Scanned: " + result.getContents(), Toast.LENGTH_LONG).show();
}
} else {
// This is important, otherwise the result will not be passed to the fragment
super.onActivityResult(requestCode, resultCode, data);
}
}
That makes an issue that any one who uses this module has to modify their MainActivity, which means the module is hard to use. So, any ideas to work it out?
You can just use react-native-rn-zxing:
npm i react-native-rn-zxing
then link it :
react-native link react-native-rn-zxing
And enjoy
First post, so please go easy.
I have an app with a handful of tabs, the first is opened on running the app.
One of the tabs is 'My Account' (a ListActivity) showing account options. I switch to this and if the user is not logged in, it, in turn, runs a UserLogon activity using the following:
Intent logonActivity = new Intent(getBaseContext(), UserLogon.class);
startActivityForResult(logonActivity, 0);
To catch the result, I use the following code:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == 0){
MainBlock ta = (MainBlock) this.getParent();
TabHost th = ta.getMyTabHost();
th.setCurrentTab(0);
finish();
}
if (requestCode == 100)
{
showAccountOptions();
}
}
In the UserLogon class, I have the usual fare; TextView's and Button's. The intention is that if the user cancels the login, it will revert to the initial tab, or if login is successful, it will show the Account Options. And this is indeed what does happen.
All good so far.
The problem I'm having is that if I cancel the login and return to the first tab, when I select the Accounts tab again, I'm not presented with the UserLogon activity. I was under the impression that finish() would close the UserLogon activity, and the Accounts activity but it would appear not.
So, my question is simply, how do I, in effect, restart the Accounts activity so that the user would be presented with the option to login once more.
We're good people and all willing to help ;-) I'll give it a shot. Still, I'm not quite sure I get that all right.
Basically you have an TabActivity which you setup and where you do something like that:
myTabHost.setOnTabChangedListener(new OnTabChangeListener(){
#Override
public void onTabChanged(String tabId) {
if (tabId.equals("account") && !loggedIn) {
Intent logonActivity = new Intent(getBaseContext(), UserLogon.class);
startActivityForResult(logonActivity, 0);
}
}});
You're basically saying that the first Activity start of UserLogon works, but the second one doesn't, right? Did you debugged to that point to check whether you reach the code which starts the activity again?
Update based on comment
Your UserLogon should always provide a result information, here's a blueprint:
public class UserLogon extends Activity {
public void onCreate(Bundle bundle) {
// ... do something ...
// if activity is canceled this will be the "last" result
setResult(RESULT_CANCELED);
}
public void checkLoginOrSomethingLikeThat() {
Intent result = new Intent();
// put your results in this intent
setResult(RESULT_OK, intent);
// close activity since we have the information we need
finish();
}
}
The parent activity which is waiting for the result should do it like that:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// it's good practice to use a constant for 0 like LOGIN_REQUEST
// otherwise you will ask yourself later "What the hell did 0 stand for?"
if(requestCode == 0){
if (resultCode == RESULT_OK) {
// get needed data from data intent and react on that
} else {
// no success, react on that
}
}
// ... I don't know what your resultCode 100 is but handle itwith the same pattern
}