Main activity class of loaded Dynamic feature is not found - android

I am trying to add dynamic feature support to my app, so I created a test app.
The test app has a main app part that loads a dynamic feature and try to execute it.
The feature module has MainActivityCalled as main activity.
What I get is that the feature loading process works because I get successful log messages, and I get the list of installed modules.
Note that the app is run on a virtual device and no real download happens, I thinks everything gets installed automatically by deployment procedure from AndroidStudio.
The fact is that I get this kind of error when trying to call the main activity of the module:
W/System.err: android.content.ActivityNotFoundException: Unable to find explicit activity class {com.example.dynamicfeature1/MainActivityCalled}; have you declared this activity in your AndroidManifest.xml?
W/System.err: at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:1805)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1523)
at android.app.Activity.startActivityForResult(Activity.java:4225)
at androidx.fragment.app.FragmentActivity.startActivityForResult(FragmentActivity.java:767)
at android.app.Activity.startActivityForResult(Activity.java:4183)
at androidx.fragment.app.FragmentActivity.startActivityForResult(FragmentActivity.java:754)
at android.app.Activity.startActivity(Activity.java:4522)
at android.app.Activity.startActivity(Activity.java:4490)
at com.example.mymodules.MainActivity$2.onClick(MainActivity.java:227)
at android.view.View.performClick(View.java:5637)
at android.view.View$PerformClick.run(View.java:22429)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
Everything is done as in the documentation at https://developer.android.com/guide/app-bundle/playcore#java
except for the explicit call to the module activity that I guessed has to be performed with an intent (no example in the above linked page).
Important part of the code you find below are:
new OnSuccessListener<Integer>() {
#Override
public void onSuccess(Integer result) {
Log.d("request feature load","success "+result);
mySessionId=result;
Set<String> installedModules = splitInstallManager.getInstalledModules();
String[] modules = new String[installedModules.size()];
installedModules.toArray(modules);
for (int i=0;i<modules.length;i++)
{
Log.d("module",modules[i]);
}
}
})
that is OK.
Then
Button button2 = findViewById(R.id.fab2);
button2.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.d("feature1","called");
Intent intent=new Intent();
intent.setClassName("com.example.dynamicfeature1","MainActivityCalled");
try{ startActivity(intent);}
catch (Exception e){
e.printStackTrace();
}
}
});
All main activities has this overidden method
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// Emulates installation of on demand modules using SplitCompat.
SplitCompat.installActivity(this);
Log.d("attachBaseContext",base.getPackageName().toString());
}
that is not called in the dynamic feature module when installed.
What is wrong with my code?
This is the Mainactivity of the app
package com.example.mymodules;
...imports...
public class MainActivity extends AppCompatActivity {
private static int MY_REQUEST_CODE=1;
Activity activity;
int mySessionId;
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// Emulates installation of future on demand modules using SplitCompat.
SplitCompat.install(this);
Log.d("attachBaseContext",base.getPackageName().toString());
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == MY_REQUEST_CODE) {
// Handle the user's decision. For example, if the user selects "Cancel",
// you may want to disable certain functionality that depends on the module.
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activity = this;
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
Button button1 = findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// Creates an instance of SplitInstallManager.
final SplitInstallManager splitInstallManager =
SplitInstallManagerFactory.create(activity);
// Creates a request to install a module.
SplitInstallRequest request =
SplitInstallRequest
.newBuilder()
// You can download multiple on demand modules per
// request by invoking the following method for each
// module you want to install.
.addModule("dynamicfeature1")
.build();
// Creates a listener for request status updates.
SplitInstallStateUpdatedListener listener =new SplitInstallStateUpdatedListener() {
#Override
public void onStateUpdate(SplitInstallSessionState state) {
if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
// Displays a dialog for the user to either “Download”
// or “Cancel” the request.
try {
splitInstallManager.startConfirmationDialogForResult(
state,
/* activity = */ activity,
// You use this request code to later retrieve the user's decision.
/* requestCode = */ MY_REQUEST_CODE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
if (state.sessionId() == mySessionId) {
switch (state.status()) {
case SplitInstallSessionStatus.INSTALLED:
Context tempNewContext=null;
try {
tempNewContext = activity.createPackageContext(activity.getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
}
final Context newContext =tempNewContext;
// If you use AssetManager to access your app’s raw asset files, you’ll need
// to generate a new AssetManager instance from the updated context.
AssetManager am = newContext.getAssets();
if (BuildCompat.isAtLeastO()) {
// Updates the app’s context with the code and resources of the
// installed module.
SplitInstallHelper.updateAppInfo(newContext);
new Handler().post(new Runnable() {
#Override public void run() {
// Loads contents from the module using AssetManager
AssetManager am = newContext.getAssets();
}
});
} else
{SplitInstallHelper.updateAppInfo(newContext);}
}
}
}
} ;
splitInstallManager.registerListener(listener);
splitInstallManager
// Submits the request to install the module through the
// asynchronous startInstall() task. Your app needs to be
// in the foreground to submit the request.
.startInstall(request)
// You should also be able to gracefully handle
// request state changes and errors. To learn more, go to
// the section about how to Monitor the request state.
.addOnSuccessListener(new OnSuccessListener<Integer>() {
#Override
public void onSuccess(Integer result) {
Log.d("request feature load","success "+result);
mySessionId=result;
Set<String> installedModules = splitInstallManager.getInstalledModules();
String[] modules = new String[installedModules.size()];
installedModules.toArray(modules);
for (int i=0;i<modules.length;i++)
{
Log.d("module",modules[i]);
}
}
})
.addOnFailureListener(new OnFailureListener() {
void checkForActiveDownloads() {
splitInstallManager
// Returns a SplitInstallSessionState object for each active session as a List.
.getSessionStates()
.addOnCompleteListener(
new OnCompleteListener<List<SplitInstallSessionState>>() {
#Override
public void onComplete(Task<List<SplitInstallSessionState>> task) {
if (task.isSuccessful()) {
// Check for active sessions.
for (SplitInstallSessionState state : task.getResult()) {
if (state.status() == SplitInstallSessionStatus.DOWNLOADING) {
// Cancel the request, or request a deferred installation.
}
}
}
}
});
}
#Override
public void onFailure(Exception e) {
Log.d("request feature load","failure "+e.getMessage());
switch (((SplitInstallException) e).getErrorCode()) {
case SplitInstallErrorCode.NETWORK_ERROR:
// Display a message that requests the user to establish a
// network connection.
break;
case SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED:
checkForActiveDownloads();
}
}
});
}
});
Button button2 = findViewById(R.id.button2);
button2.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.d("feature1","called");
Intent intent=new Intent();
intent.setClassName("com.example.dynamicfeature1","MainActivityCalled");
try{ startActivity(intent);}
catch (Exception e){
e.printStackTrace();
}
}
});
Button button3 = findViewById(R.id.button3);
button3.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.d("feature2","called");
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
here's the main app manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution"
package="com.example.mymodules">
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:name="com.google.android.play.core.splitcompat.SplitCompatApplication"
android:theme="#style/AppTheme">
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:theme="#style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Then there is the MainActivity of the dyamic feature module
package com.example.dynamicfeature1;
...imports...
public class MainActivityCalled extends AppCompatActivity {
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// Emulates installation of on demand modules using SplitCompat.
SplitCompat.installActivity(this);
Log.d("attachBaseContext",base.getPackageName().toString());
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Snackbar.make(view, "1-Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
}
}
and the module manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution"
package="com.example.dynamicfeature1">
<dist:module
dist:instant="false"
dist:title="#string/title_dynamicfeature1">
<dist:delivery>
<dist:on-demand />
</dist:delivery>
<dist:fusing dist:include="false" />
</dist:module>
<application>
<activity
android:name=".MainActivityCalled"
android:label="#string/title_activity_main"
android:theme="#style/AppTheme.NoActionBar"></activity>
</application>
</manifest>

The right instruction to call the activity is
intent. setClassName(BuildConfig.APPLICATION_ID, "com.example.dynamicfeature.MainActivityCalled");
It works, the activity gets called.
Note that BuildConfig.APPLICATION_ID is
com.example.mymodules
If this string is used in the dynamic modules too, you can make cross-calls:
-from one module to another
-from one module to the main app
but you do not want to use BuildConfig.APPLICATION_ID in modules because there it is a different value, so the main string value has to be put into a variable.

Related

Truecaller SDK Error : No compatible client available. Please change your scope

I am trying to implement truecaller in my app and i am doing exactly written in Truecaller Docs.
But still it gives me error.
I have tried googling the problem but still couldn't find the solution.
Here is the error:
No compatible client available. Please change your scope
Here is my code:
public class MainActivity extends FragmentActivity implements ITrueCallback {
private ViewPager2 viewPager2;
private List < Integer > imagesList;
private Button btnContinue, btnLoginTruecaller;
private EditText etPhone;
private Preferences preferences;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Constants.removeStatusBar(this);
TruecallerSdkScope trueScope = new TruecallerSdkScope.Builder(this, sdkCallback)
.consentMode(TruecallerSdkScope.CONSENT_MODE_BOTTOMSHEET)
.buttonColor(Color.parseColor("#000000"))
.buttonTextColor(Color.parseColor("#000000"))
.loginTextPrefix(TruecallerSdkScope.LOGIN_TEXT_PREFIX_TO_GET_STARTED)
.loginTextSuffix(TruecallerSdkScope.LOGIN_TEXT_SUFFIX_PLEASE_VERIFY_MOBILE_NO)
.ctaTextPrefix(TruecallerSdkScope.CTA_TEXT_PREFIX_USE)
.buttonShapeOptions(TruecallerSdkScope.BUTTON_SHAPE_ROUNDED)
.privacyPolicyUrl("<<YOUR_PRIVACY_POLICY_LINK>>")
.termsOfServiceUrl("<<YOUR_PRIVACY_POLICY_LINK>>")
.footerType(TruecallerSdkScope.FOOTER_TYPE_NONE)
.consentTitleOption(TruecallerSdkScope.SDK_CONSENT_TITLE_LOG_IN)
.sdkOptions(TruecallerSdkScope.SDK_OPTION_WITHOUT_OTP)
.build();
TruecallerSDK.init(trueScope);
btnContinue = findViewById(R.id.btnContinue);
etPhone = findViewById(R.id.etPhone);
btnLoginTruecaller = findViewById(R.id.btnLoginTruecaller);
TruecallerSDK.getInstance().getUserProfile(this);
btnLoginTruecaller.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {}
});
preferences = new Preferences(this);
if (preferences.isLoggedin()) {
Intent intent = new Intent(this, HomeActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
imagesList = new ArrayList < > ();
imagesList.add(R.drawable.black);
imagesList.add(R.drawable.pubg);
// ViewPagerAdapter adapter = new ViewPagerAdapter(this,imagesList);
// viewPager2.setAdapter(adapter);
btnContinue.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
preferences.setMobileNumber(etPhone.getText().toString());
Intent intent = new Intent(MainActivity.this, OTPActivity.class);
intent.putExtra("phone", etPhone.getText().toString());
startActivity(intent);
}
});
}
private final ITrueCallback sdkCallback = new ITrueCallback() {
#Override
public void onSuccessProfileShared(#NonNull TrueProfile trueProfile) {
}
#Override
public void onFailureProfileShared(#NonNull TrueError trueError) {
}
#Override
public void onVerificationRequired(TrueError trueError) {
}
};
#Override
public void onSuccessProfileShared(#NonNull TrueProfile trueProfile) {
}
#Override
public void onFailureProfileShared(#NonNull TrueError trueError) {
}
#Override
public void onVerificationRequired(TrueError trueError) {
}
}
Here is the truecaller docs i am following:
https://docs.truecaller.com/truecaller-sdk/android/integrating-with-your-app/setup
Thanks for sharing the above information.
The exception that you are facing :
No compatible client available. Please change your scope
comes only in the case where you are calling a method from TruecallerSDK that is not in the scope which you provide while initialising the SDK.
For instance, in case where Truecaller app is not installed or Truecaller app is installed but not logged and you have mentioned the sdkOptions as TruecallerSdkScope.SDK_OPTION_WIHTOUT_OTP then on calling TruecallerSDK.getInstance().getUserProfile() method you will face this exception.
To refrain from facing this again you can put a check that if TruecallerSDK.getInstance.isUsable turns out to be True, then only call TruecallerSDK.getInstance.getUserProfile or you can change the sdkOptions scope to TruecallerSdkScope.SDK_OPTION_WITH_OTP to verify both Truecaller and Non-Truecaller users
In case if you face any queries in the future, please feel free to reach us via our support channel https://developer.truecaller.com/support for a faster and dedicated response.
Regard,
Parth

Communication between BroadcastReceiver and Activity using interface

Folks!
I'm trying to implement BroadcastReceiver for listening to incoming Message using SMSRetrieverAPI.
I'm getting messages in Broadcast's onReceive method but after that, I have to pass that Message String in activity without restarting it, So I have implemented interface.
I have tried below code but, it throws NullPointerException for mOTPReceiveListener. Something didn't work perfectly so please correct me.
SMSBroadcastReceiver is like below
public class SMSBroadcastReceiver extends BroadcastReceiver {
OTPReceiveListener mOTPReceiveListener = null;
public void InitOTPReceiveListener(OTPReceiveListener otpreceivelistener){
this.mOTPReceiveListener = otpreceivelistener;
}
#Override
public void onReceive(Context context, Intent intent) {
if (SmsRetriever.SMS_RETRIEVED_ACTION.equals(intent.getAction())) {
Bundle extras = intent.getExtras();
Status status = (Status) extras.get(SmsRetriever.EXTRA_STATUS);
switch(status.getStatusCode()) {
case CommonStatusCodes.SUCCESS:
// Get SMS message contents
String message = (String) extras.get(SmsRetriever.EXTRA_SMS_MESSAGE);
Log.e("VERIFY","Full Message : "+message);
if (mOTPReceiveListener != null) {
String[] temp = message.split("-");
String[] msg = temp[1].split(" ");
message = msg[0];
Log.e("VERIFY","message : "+message);
mOTPReceiveListener.onOTPReceived(message);
}
break;
case CommonStatusCodes.TIMEOUT:
// Waiting for SMS timed out (5 minutes)
mOTPReceiveListener.onOTPTimeOut();
break;
}
}
}
public interface OTPReceiveListener {
void onOTPReceived(String otp);
void onOTPTimeOut();
}
}
and the activity code
public class VerificationActivity extends BaseActivity implements SMSBroadcastReceiver.OTPReceiveListener
{
Context mContext;
Button btn_verify_submit;
TextView text_resend_otp;
private String TAG = "VERIFY";
private String PHONE = null;
private SmsRetrieverClient mSmsRetrieverClient;
private SMSBroadcastReceiver mSMSBroadcastReceiver;
SMSBroadcastReceiver.OTPReceiveListener mOTPReceiveListener;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_verification);
initView();
setClickListener();
PHONE = getIntent().getStringExtra("phone");
Log.e(TAG,"phone : "+PHONE);
mSmsRetrieverClient = SmsRetriever.getClient(this);
startVerify();
mOTPReceiveListener = this;
mSMSBroadcastReceiver = new SMSBroadcastReceiver();
mSMSBroadcastReceiver.InitOTPReceiveListener(this);
}
private void initView()
{
layout_verification_main = findViewById(R.id.layout_verification_main);
overrideFonts(layout_verification_main,VerificationActivity.this);
edt_verify_code = findViewById(R.id.edt_verify_code);
btn_verify_submit = findViewById(R.id.btn_verify_submit);
text_resend_otp = findViewById(R.id.text_resend_otp);
}
private void setClickListener()
{
btn_verify_submit.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Call OTP Match API
}
});
text_resend_otp.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Call Resend OTP API
}
});
}
private void startVerify() {
Task<Void> task = mSmsRetrieverClient.startSmsRetriever();
task.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.e(TAG, "SmsRetrievalResult start onSuccess.");
}
});
task.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.e(TAG, "SmsRetrievalResult start failed.", e);
}
});
}
#Override
public void onOTPReceived(String otp) {
edt_verify_code.setText("" + otp);
Toast.makeText(mContext, "Message OTP : " + otp, Toast.LENGTH_SHORT).show();
Log.e("VERIFY","otp in activity : "+otp);
}
#Override
public void onOTPTimeOut() {
}
}
and I have registered in Manifest like this
<receiver android:name=".broadcast.SMSBroadcastReceiver" android:exported="true">
<intent-filter>
<action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED"/>
</intent-filter>
</receiver>
Am I missing something?
The SMSBroadcastReceiver instance created from the definition in your manifest is NOT the same one you created in VerificationActivity.onCreate().
You can register the broadcast receiver from your activity using Context.registerReceiver().
To contact between activity and receiver what you are try is wrong as your listener is dependent on activities onCreate() i.e on lifecycle of Activity.
So to solve this you need to follow below step's:
Solution
Be sure that activity is alive by starting the activity from your receiver (startActitvity() using intent ) again. check this thread if you don't want to restart the activity again and want to be sure that activity is on screen.
Send a broadcast from your receiver to activity read this thread. No need of listener as activity can directly listen to message
you can combine both solution's for you own use case it will work.

Braintree PayPal Sandbox keeps redirecting

I have added a simple non drop-in paypal integration in sandbox mode to my app. Here is a test activity with a single "Pay" button:
public class PaypalPaymentAcivity extends Activity implements PaymentMethodNonceCreatedListener {
private BraintreeFragment mBraintreeFragment;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_paypal);
findViewById(R.id.payButton).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
startPayment();
}
});
}
private void startPayment() {
try {
mBraintreeFragment = BraintreeFragment.newInstance(this, "...");
PayPalRequest request = new PayPalRequest("1")
.currencyCode("USD")
.intent(PayPalRequest.INTENT_AUTHORIZE);
PayPal.requestOneTimePayment(mBraintreeFragment, request);
} catch (InvalidArgumentException e) {
e.printStackTrace();
}
}
#Override
public void onPaymentMethodNonceCreated(PaymentMethodNonce paymentMethodNonce) {
}
}
However once the PayPal browser window comes up after clicking the buttons it just keeps popping up over and over, and never returns to my activity.
Anyone had a successful integration like this?

How to use UseButton in android

i need UseButton in my app to link uber app.For this i have implemented the following code.In useButton Developer site, it has been said to submit for review. while filling the review form, its asking screenshot with a working button. but my button in the screen is not visible. how can it be working button.
please help me.
public class MainActivity2 extends AppCompatActivity {
RelativeLayout rootLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
rootLayout = (RelativeLayout) findViewById(R.id.rootlayout);
final ButtonDropin buttonDropin = new ButtonDropin(MainActivity2.this);
buttonDropin.setButtonId("btn-11cd09c63aaa7c02");
buttonDropin.setBackgroundResource(R.drawable.ic_app_button);
buttonDropin.setTextSize_Button(getResources().getDimensionPixelSize(R.dimen.my_text_size));
ButtonContext contexts = null;
try {
final RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
buttonDropin.setLayoutParams(layoutParams);
rootLayout.addView(buttonDropin);
contexts = ButtonContext.withSubjectLocation(new Location("Button HQ", 11.9362587, 79.8268325));
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
final android.location.Location userLocations = new LocationProvider(this).getBestLocation();
if (userLocations != null) {
contexts.setUserLocation(new Location(userLocations));
}
}catch (Exception e){
Toast.makeText(MainActivity2.this,"Exception :"+e,Toast.LENGTH_LONG).show();
}
buttonDropin.prepareForDisplay(contexts, new ButtonDropin.Listener() {
#Override
public void onPrepared(final boolean isReady) {
Toast.makeText(MainActivity2.this,"IsReady:"+isReady,Toast.LENGTH_LONG).show();
}
#Override
public void onClick(ButtonDropin buttonDropin) {
Toast.makeText(MainActivity2.this,"buttonDropin onclick",Toast.LENGTH_LONG).show();
}
});
}}
Buttons that need to be reviewed will still render. Also, Buttons will only render if there is inventory to display (for this example, will there be Ubers to take from where the user is to where the user is going, which set in the Context).
First, add the Button SDK to your app's build.gradle file
compile 'com.usebutton:android-sdk:5+'
Then, add Button to your AndroidManifest.xml file
<application
<activity
<!-- your activities -->
</activity>
<!--Button SDK-->
<meta-data android:name="com.usebutton.applicationid" android:value="YOUR_BUTTON_APP_ID"/>
</application>
Then, import the SDK at the top of the Activity and start it. This should be called when your application class is created.
import com.usebutton.sdk.Button;
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
Button.getButton(this).start();
}
}
Then, add the Button to your view:
<com.usebutton.sdk.ButtonDropin
android:id="#+id/main_dropin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
And then load the action for your Button in your Activity file:
final ButtonDropin buttonDropin = (ButtonDropin) findViewById(R.id.main_dropin);
// Set location to go to
final Location location = new Location("LOCATION_NAME", LATITUDE, LONGITUDE);
// Create Button Context
final ButtonContext buttonContext = ButtonContext.withSubjectLocation(location);
// Get Button Action and display if there is inventory
Button.getButton(this).getAction("YOUR_BUTTON_ID", buttonContext, "my-pub-ref", new Button.ActionListener() {
#Override
public void onAction(final AppAction action) {
// Display Button
buttonDropin.prepareWithAction(action);
}
#Override
public void onNoAction() {
// Don't display Button
buttonDropin.setVisibility(View.GONE);
}
});

Android - How to get a Session from my Spell Checker Service?

I am trying to implement a spell checker service as described here called SampleSpellCheckerService but it seems the tutorial is incomplete and the source code for it does not seem to be available.
I am struggling with how to get a session from my spell checker service in the setSuggestionsFor() method of my activity, as highlighted here:
public class SpellCheckerSettingsActivity extends AppCompatActivity implements SpellCheckerSession.SpellCheckerSessionListener {
private static final String LOG_TAG = SpellCheckerSettingsActivity.class.getSimpleName();
private TextView textView = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_spell_checker_settings);
final EditText editText = (EditText)findViewById(R.id.editText);
textView = (TextView)findViewById(R.id.textView);
Button button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
fetchSuggestionsFor(editText.getText().toString());
}
});
startService(new Intent(this, SampleSpellCheckerService.class));
}
private void fetchSuggestionsFor(String input){
Log.d(LOG_TAG, "fetchSuggestionsFor(\"" + input + "\")");
/***************************************************
*
* This line is invalid. What do I replace it with?
*
***************************************************/
SpellCheckerSession session = SampleSpellCheckerService.getSession();
TextInfo[] textInfos = new TextInfo[]{ new TextInfo(input) };
int suggestionsLimit = 5;
session.getSentenceSuggestions(textInfos, suggestionsLimit);
}
#Override
public void onGetSuggestions(SuggestionsInfo[] results) {
Log.d(LOG_TAG, "onGetSuggestions(" + results + ")");
runOnUiThread(new Runnable() {
#Override
public void run() {
textView.setText("Suggestions obtained (TODO - get from results[])");
}
});
}
#Override
public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) {
Log.d(LOG_TAG, "onGetSentenceSuggestions(" + results + ")");
if (results != null) {
final StringBuffer sb = new StringBuffer("");
for (SentenceSuggestionsInfo result : results) {
int n = result.getSuggestionsCount();
for (int i = 0; i < n; i++) {
int m = result.getSuggestionsInfoAt(i).getSuggestionsCount();
for (int k = 0; k < m; k++) {
sb.append(result.getSuggestionsInfoAt(i).getSuggestionAt(k))
.append("\n");
}
sb.append("\n");
}
}
runOnUiThread(new Runnable() {
#Override
public void run() {
textView.setText(sb.toString());
}
});
}
}
#Override
public void onDestroy() {
stopService(new Intent(this, SampleSpellCheckerService.class));
super.onDestroy();
}
}
So what is the correct way to get a session from SampleSpellCheckerService?
For completeness, here is my spell checker service class:
public class SampleSpellCheckerService extends SpellCheckerService {
public static final String LOG_TAG = SampleSpellCheckerService.class.getSimpleName();
public SampleSpellCheckerService() {
Log.d(LOG_TAG, "SampleSpellCheckerService");
}
#Override
public void onCreate() {
super.onCreate();
Log.d(LOG_TAG, "SampleSpellCheckerService.onCreate");
}
#Override
public Session createSession() {
Log.d(LOG_TAG, "createSession");
return new AndroidSpellCheckerSession();
}
private static class AndroidSpellCheckerSession extends SpellCheckerService.Session {
#Override
public void onCreate() {
Log.d(LOG_TAG, "AndroidSpellCheckerSession.onCreate");
}
#Override
public SentenceSuggestionsInfo[] onGetSentenceSuggestionsMultiple(TextInfo[] textInfos, int suggestionsLimit) {
Log.d(LOG_TAG, "onGetSentenceSuggestionsMultiple");
SentenceSuggestionsInfo[] suggestionsInfos = null;
//suggestionsInfo = new SuggestionsInfo();
//... // look up suggestions for TextInfo
return suggestionsInfos;
}
#Override
public SuggestionsInfo onGetSuggestions(TextInfo textInfo, int suggestionsLimit) {
Log.d(LOG_TAG, "onGetSuggestions");
SuggestionsInfo suggestionsInfo = null;
//suggestionsInfo = new SuggestionsInfo();
//... // look up suggestions for TextInfo
return suggestionsInfo;
}
#Override
public void onCancel() {
Log.d(LOG_TAG, "onCancel");
}
}
}
Here is my manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example">
<permission android:name="android.permission.BIND_TEXT_SERVICE" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<service
android:name="com.example.SampleSpellCheckerService"
android:label="#string/app_name"
android:enabled="true"
android:permission="android.permission.BIND_TEXT_SERVICE">
<intent-filter>
<action android:name="android.service.textservice.SpellCheckerService" />
</intent-filter>
<meta-data
android:name="android.view.textservice.scs"
android:resource="#xml/spellchecker" />
</service>
<activity android:name="com.example.SpellCheckerSettingsActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
And here is my spellchecker.xml:
<?xml version="1.0" encoding="utf-8"?>
<spell-checker
xmlns:android="http://schemas.android.com/apk/res/android"
android:label="#string/spellchecker_name"
android:settingsActivity="com.example.SpellCheckerSettingsActivity">
<subtype
android:label="#string/subtype_generic"
android:subtypeLocale="en" />
/>
<subtype
android:label="#string/subtype_generic"
android:subtypeLocale="en_GB" />
/>
</spell-checker>
NB - I am testing with a Samsung device.
As far as I can see from the docs and some sample code, there seems to be some misconception of the android Spell Checking API, that result in your error.
As far as I can tell you can't call your service directly since the APIs goal is for you to define a spellchecker that the user has to select from the system settings first. Basically you mixed up the settings activity (that is displayed for service related settings) with a test activity for your service.
Some better tutorials are written in the android dev blog and here, some sample code for a testing client and an rudimentary example service could be found between the mirrored android samples on github.
What you got so far is the sample service (though the linked samples provide some more code to see how the methods could be implemented), you have your spellchecker.xml needed for locale definition and the spellchecker name appearing in the settings, you already have a settings activity (as defined in your spellchecker.xml, but not needed as long as you don't need any preferences) and you have an activity implementing your SpellCheckerSessionListener (although you named it as settings activity).
What you'd still need to do, is go to your settings -> Language & keyboard -> activate Spell checker and choose your spell checker.
To get a session from that spellchecker you can then make a call to the API with
final TextServicesManager tsm = (TextServicesManager) getSystemService(
Context.TEXT_SERVICES_MANAGER_SERVICE);
mScs = tsm.newSpellCheckerSession(null, null, this, true);
as seen in the samples.
Edit:
if you don't need any settings for your service, you can remove the xml attribute from your xml:
android:settingsActivity="com.example.SpellCheckerSettingsActivity"

Categories

Resources