cancel
Showing results for 
Search instead for 
Did you mean: 

SAP BTP SDK Android onboarding customization implementation

hima_ods
Explorer
849

Hi,

I want to make custom changes in SAP BTP Android onboarding screens. For ex. I need to make my own login screen(username and password enter screen).

I do not require the passcode screens.

Is there any possibility to do customization in onboarding screens?

Note: I'm using SAP BTP SDK Version - 5.1.2

I would like to thanks in advance for your help.

0 Kudos

Hi, are you using Flows to do the onboarding? Could you help attach the screen shot for the screens you would like to customize? Thanks.

hima_ods
Explorer

Hi,

I want to use my own login screens like below attached screenshots.

required-uipng.png

not-required-uipng.png

I'm using the below code.

FlowOptions options = new FlowOptions(InfoMessageOption.TOAST, ActivationOption.CHOOSE_BETWEEN_DS_QR, true,
true, true, true, temp.getApplicationTheme(),
true, true, true, OnboardingOrientation.UNSPECIFIED,
OAuth2WebOption.WEB_VIEW, SignedQRCodeOption.UNSPECIFIED, true, true, true);
FlowContext flowContext = new FlowContextBuilder()
.setApplication(appConfig)
.setMultipleUserMode(true)
.setFlowStateListener(new WizardFlowStateListener((SAPWizardApplication) getApplication()))
.setFlowOptions(temp).build();
Flow.start(this, flowContext, new Function3<Short, Integer, Intent, Unit>() {
@Override
public Unit invoke(Short requestCode, Integer resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
((SAPWizardApplication) getApplication()).isApplicationUnlocked = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
registerActivityLifecycleCallbacks(AppLifecycleCallbackHandler.getInstance());
}
Intent intent = new Intent(getApplication(), OfflineDataAccessActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finishAffinity();
}
return null;
}

});

This screen can be customized, but can not be same as required. The title and description part above the username/password only support text, so you can not put an image there. Attached a customized screen for your reference.

customized-screen.png
ragisaikiran
Explorer

Hii,

Is it possible to complete the onboarding without any SAP provided UI?

I just want to complete onboarding by calling SAP provided methods or API's .

Is it possible?

jameszhangyn
Advisor
Advisor

Hi Hima, to answer your question, we do have foundation component which can be used for onboarding without any UI, API reference: https://help.sap.com/doc/f53c64b93e5140918d676b927a3cd65b/Cloud/en-US/docs-en/reference/android/java....

But before we reach to that conclusion, could i understand more about your setup?

- I believe your application will connect to Mobile Service on BTP CF or NEO, correct? what is your security configuration there? do you configure as SMAL or Basic? the reason i ask this question is the login screen you provided is more like a IDP form-based login screen. If you are using SAML, then our flow will open the WebVIew or CCT to load the IDP login page. you no need to develop your own login screen.

- To disable the passcode, you just need to goto our cockpit to disable it, refer to https://help.sap.com/doc/f53c64b93e5140918d676b927a3cd65b/Cloud/en-US/docs-en/guides/features/securi....

I also like to ask a deeper question, why you don't like user to enter a short passcode but normally the long password to login client? is it because of using the same device with mutiple users? If that is the reason, we have Multiple-Users solution now. each user can owns their own passcode to login the same device, refer to https://help.sap.com/doc/f53c64b93e5140918d676b927a3cd65b/Cloud/en-US/docs-en/guides/features/shared....

hima_ods
Explorer

Hi,

As of now I'm using basic configuration.

Previously I just provided sample login screen(required-uipng.png) as I don't want to expose my login screen publicly. As per my requirement there are so many controls available in my login screen. So, it is not possible with SAP provided UI.

I still confused with the below provided link of foundation component.

https://help.sap.com/doc/f53c64b93e5140918d676b927a3cd65b/Cloud/en-US/docs-en/reference/android/java...

Can you please help me to complete the onboarding by sharing the relevant code?

Accepted Solutions (0)

Answers (2)

Answers (2)

former_member410600
Participant
0 Kudos

Hi Hima,

Including onboarding , what other feature do you like to use without flow? For example, usage , client policy , Log, Usage , Push etc. Would you please tell us the exact feature list ?

hima_ods
Explorer

Hi,

I don't want to use SAP provided UI in my entire application.

For now, as per the requirement I have to onboard the user using my own login screen which has my own UI.

After successful onboarding, user should navigate to the dashboard screen.

I don't want to show any other screens(For example, usage , client policy , Log, Usage , Push etc) to user.

Can you please help me by sharing the code snippet or relevant document?

former_member410600
Participant

Here is a basic authentication code snippet for your reference.

1.Disable UI callback in onCreate method of application.kt

AuthenticationUiCallbackManager.setAuthenticationUiCallback { false }

2.Implement your own BasicAuthDialogAuthenticator.

class CustomBasicAuthAuthenticator(private val user: String, private val pwd: String) :
BasicAuthDialogAuthenticator() {
override fun authenticate(route: Route?, response: Response): Request? {
return response.request.newBuilder().header("Authorization", Credentials.basic(user, pwd)).build()
}
}
3.Establish connection with OKHttp client. Do following action when response is successful. Hope it's helpful.
binding.login.setOnClickListener {
val user = binding.etUser.text.toString()
val pwd = binding.etPwd.text.toString()
val basicAuthOkHttpClient = OkHttpClient.Builder()
.addInterceptor(AppHeadersInterceptor(appid, deviceId, applicationVersion))
.authenticator(CustomBasicAuthAuthenticator(user, pwd))
.cookieJar(WebkitCookieJar())
.retryOnConnectionFailure(true)
.build()
val request = Request.Builder()
.get()
.url("$backendUrl/$appid")
.build()
CoroutineScope(Dispatchers.IO).launch {
basicAuthOkHttpClient.newCall(request = request).execute().use {
if(it.isSuccessful){
//To-do
requireActivity().runOnUiThread{
Toast.makeText(requireContext(),"Success with Code ${it.code}",Toast.LENGTH_LONG).show()
}
}
}
}
}
hima_ods
Explorer
0 Kudos

Hi lingnage12

Thank you for your support. The above given code snippet is working fine and user is onboarding now.

But if we use this code for onboarding then I am getting issue to open the stores.

I am getting error while executing the below statement

Problematic line of code: 
UserSecureStoreDelegate.getInstance().getOfflineEncryptionKey()
                   (OR)
UserSecureStoreDelegate.getInstance().getData(OFFLINE_DATASTORE_ENCRYPTION_KEY)
                   (OR)<br>UserSecureStoreDelegate.getInstance().getRuntimeMultipleUserModeAsync()<br><br>Error I am getting:  "lateinit property userStore has not been initialized"


This is working fine while using flows.

But while using foundation I am getting this error.

How to open the stores when using foundation for onboarding?
Can you please help me here.

former_member410600
Participant
0 Kudos

Hi Hima,

The UserSecureStoreDelegate is part of Flow. It need flow start correctly and it will be initialzied correctly. Since you have not onboard with flow ,you will meet some problems with this class . Can you specify what you want to do next ? Like Initialize an offline data source? Or something else?

hima_ods
Explorer
0 Kudos

Hi lingnage12,

As you expected I am getting some issues with some classes(UserSecureStoreDelegate, FlowContextRegistry etc).

ClientProvider.get() also not working here even it is from foundation library.

My next step is I need to download the required entities to offline stores.
Initialize the offline store -> Open the stores ->Download the entities -> Navigate to Dashboard and show the data using offline stores data.
Later the data has to show in UI.
I have taken some code from wizard generated project.
As wizard generated project has used flows dependency internally to generate the project, I am getting issues.

former_member410600
Participant
0 Kudos

Hi Hima,

Before you do onboarding with OkHttpClient, please invoke SettingsProvider.set(settingsParameters) and ClientProvider.set(okHttpClient).

And after successful onboarding, then you can initialize your own OfflineProvider with below code snippet:

val offlineODataParameters = OfflineODataParameters()
val offlineDelegate = OfflineODataDelegateImpl()
offlineODataProvider = OfflineODataProvider(
url,
offlineODataParameters,
ClientProvider.get(),
offlineDelegate
)

And then with proxy class and OfflineOdataProvider , you could initialize your own DataService class.

hima_ods
Explorer
0 Kudos

Hi lingnage12,

Still I'm getting the error, after using the above code.

2023-03-31 16:49:54.441 19927-19927/com.poc.btplib E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.poc.btplib, PID: 19927
kotlin.UninitializedPropertyAccessException: lateinit property userStore has not been initialized
at com.sap.cloud.mobile.foundation.user.UserStoreManager.getUserStore(UserStoreManager.kt:31)
at com.sap.cloud.mobile.foundation.user.UserStoreManager.isStoreOpen(UserStoreManager.kt:128)
at com.sap.cloud.mobile.flowv2.securestore.UserSecureStoreDelegate.checkRequirement(UserSecureStoreDelegate.kt:29)
at com.sap.cloud.mobile.flowv2.securestore.UserSecureStoreDelegate.access$checkRequirement(UserSecureStoreDelegate.kt:24)
at com.sap.cloud.mobile.flowv2.securestore.UserSecureStoreDelegate$saveData$1.invokeSuspend(UserSecureStoreDelegate.kt:76)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@40880db, Dispatchers.Main]

former_member410600
Participant
0 Kudos

Hi Hima,

According to your log, you are still using UserSecureStoreDelegate. Since you have not do onboarding with Flow, this class cannot be initialized correctly.

former_member410600
Participant
0 Kudos

Hi Hima,

I have 2 questions.

1. If your app is killed , then user launches app again, do you want user to input basic credential again?

2. If user switch happend , do you want offline store changed to new user also?

hima_ods
Explorer
0 Kudos

Hi lingnage12,

1. If your app is killed , then user launches app again, do you want user to input basic credential again?

Yes. User must login every time after app relaunched.

2. If user switch happend , do you want offline store changed to new user also?

Yes, It has to support multi user.

As of now I'm developing the app from scratch using java.

While doing the initial sync I'm getting the below error in RealMe (Android 13) device.

2023-04-03 17:47:53.993 8748-20234/com.poc.btplib E/com.sap.cloud.mobile.odata.offline: [Thread-12] [-10346] The download failed to establish a socket connection to the server.

2023-04-03 17:47:53.996 8748-20234/com.poc.btplib E/class com.poc.btplib.service.OfflineOpenWorker: [Thread-12] [-10346] The download failed to establish a socket connection to the server.

Here to do the offline data downloading functionality I'm using AsyncTask.

former_member410600
Participant
0 Kudos

Hi Hima,

For offline operation, you need to initialize OfflineOdataProvider and corresponding DataService. Then Open OfflineOdataProvider , then you can download the entites in your AsyncTask. Can you share the project with me if you don't mind?

hima_ods
Explorer
0 Kudos

Hi lingnage12,

Now the app is working fine after restarting the problematic device.

It seems the device problem and app is working fine in other devices.

Please update me regarding user login after relaunching the app (User must login every time after app relaunches).

Switch user offline data handling in multi user mode.

former_member410600
Participant
0 Kudos

Hi Hima,

In your case ,you need to follow below steps:

Preparation Steps:

a.Enable Allow Upload of Pending Changes from Previous User (Enable Multiple User Mode) on Mobile Service Cockpit . Path :

Mobile Settings Exchange -> Shared Devices

b.Make sure Settings->Security->Trust configuration is configured. and Test configuration successfully.

If not ,you can download metadata here and upload to your subaccount (Go to your subaccount , left menu , security -> Trust configuration -> Click New Trust Configuration button and upload metadata)

c.Make sure SSO Mechanism/ Authentication of your connectivity is not basic.

Client:

1.After user onboards successfully , you can retrieve user:

user = SDKInitializer.getService(UserService::class)?.retrieveUser()

2.When initialize offline odata provider , please set current user with the user you got in step 1. and set isForceUploadOnUserSwitch to true.

val offlineODataParameters = OfflineODataParameters().apply {
storeName = OFFLINE_DATASTORE
isForceUploadOnUserSwitch = true
currentUser = user?.id
}

Then you can try offline operation with different user.

Hope it could solve your problem.

hima_ods
Explorer
0 Kudos

Hi lingnage12,

I want to fetch (online) metadata from EntitiesMetadataText.xml dynamically in SAP BTP SDK for Android using foundation library.

Can you please help?

weili3
Advisor
Advisor
0 Kudos

Hi Hima,

Does your onboarding code use flows module? Could you share your onboarding code?

hima_ods
Explorer

Hi,

We are using below dependency. Please help here, we got stuck for few days on this issue.

implementation "com.sap.cloud.android:flowsv2:$versions.sapCloudAndroidSdk"

Main thing here is I need my own login screen (UI).

LaunchScreenSettings settings = new LaunchScreenSettings.Builder()
                .setDemoButtonVisible(false)
                .setHeaderLineLabel("Welcome")
                .setPrimaryButtonText("Get Started")
                .setFooterVisible(true)
                .setUrlTermsOfService("http://www.sap.com")
                .setUrlPrivacy("http://www.sap.com")
                .addInfoViewSettings
                        (new LaunchScreenSettings.LaunchScreenInfoViewSettings(
                                R.drawable.ic_launcher_foreground,
                                getString(R.string.app_name),
                                "Explore the SAP BTP for Android wth the app")
                        ).build();
        binding.launchScreen.initialize(settings);
        binding.launchScreen.setPrimaryButtonOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startConfigurationLoader();
            }
        });
 private void startConfigurationLoader() {
        ConfigurationLoaderCallback callback = new ConfigurationLoaderCallback() {
            @Override
            public void onCompletion(@Nullable ProviderIdentifier providerIdentifier, boolean isSuccess) {
                if (isSuccess) {
                    startFlow();
                } else {
                    new DialogHelper(getApplication(), R.style.OnboardingDefaultTheme_Dialog_Alert)
                            .showOKOnlyDialog(
                                    fManager,
                                    "Configuration loader failed to get configuration data.",
                                    null, null, null
                            );
                }
            }

            @Override
            public void onError(@NonNull ConfigurationLoader configurationLoader, @NonNull ProviderIdentifier providerIdentifier,
                                @NonNull UserInputs userInputs, @NonNull ConfigurationProviderError configurationProviderError) {
                new DialogHelper(getApplication(), R.style.OnboardingDefaultTheme_Dialog_Alert)
                        .showOKOnlyDialog(
                                fManager, String.format(
                                        getResources().getString(
                                                R.string.config_loader_on_error_description
                                        ),
                                        providerIdentifier.toString(), configurationProviderError.getErrorMessage()
                                ),
                                null, null, null
                        );
                configurationLoader.processRequestedInputs(new UserInputs());
            }

            @Override
            public void onInputRequired(@NonNull ConfigurationLoader configurationLoader, @NonNull UserInputs userInputs) {
                configurationLoader.processRequestedInputs(new UserInputs());
            }
        };
        ConfigurationProvider[] providers = new ConfigurationProvider[1];
        providers[0] = new FileConfigurationProvider(MainActivity.this, "sap_mobile_services");
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                ConfigurationLoader loader = new ConfigurationLoader(MainActivity.this, callback, providers);
                loader.loadConfiguration();
            }
        });
    }
  private void startFlow() {
        AppConfig appConfig = prepareAppConfig();
        if (appConfig == null) return;
        FlowOptions temp = new FlowOptions();
        FlowOptions options = new FlowOptions(InfoMessageOption.TOAST, ActivationOption.CHOOSE_BETWEEN_DS_QR, true,
                true, true, true, temp.getApplicationTheme(),
                true, true, true, OnboardingOrientation.UNSPECIFIED,
                OAuth2WebOption.WEB_VIEW, SignedQRCodeOption.UNSPECIFIED, true, true, true);
        FlowContext flowContext = new FlowContextBuilder()
                .setApplication(appConfig)
                .setMultipleUserMode(true)
                .setFlowStateListener(new WizardFlowStateListener((SAPWizardApplication) getApplication()))
                .setFlowOptions(options).build();
        Flow.start(this, flowContext, new Function3<Short, Integer, Intent, Unit>() {
            @Override
            public Unit invoke(Short requestCode, Integer resultCode, Intent data) {
                if (resultCode == Activity.RESULT_OK) {
                    ((SAPWizardApplication) getApplication()).isApplicationUnlocked = true;
                    Intent intent = new Intent(getApplication(), OfflineDataAccessActivity.class);
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
                    startActivity(intent);
                }
                return null;
            }
        });
    }

    private AppConfig prepareAppConfig() {
        try {
            JSONObject configData = DefaultPersistenceMethod.getPersistedConfiguration(this);
            return AppConfig.createAppConfigFromJsonString(configData.toString());
        } catch (ConfigurationPersistenceException e) {
            Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
        }
        return null;
    }
ragisaikiran
Explorer
0 Kudos

Hi Experts,

Can anyone please acknowledge this question?

former_member410600
Participant
0 Kudos

Hi Hima,

Including onboarding , what other feature do you like to use without flow? For example, usage , client policy , Log, Usage , Push etc. Would you please tell us the exact feature list ?

hima_ods
Explorer

Hi,

I don't want to use SAP provided UI in my entire application.

For now, as per the requirement I have to onboard the user using my own login screen which has my own UI.

After successful onboarding, user should navigate to the dashboard screen.

I don't want to show any other screens(For example, usage , client policy , Log, Usage , Push etc) to user.

Can you please help me by sharing the code snippet or relevant document?