How to access Android assets folder in React-native projects? - react-native

I'm trying to build a Android module for React-native,
But it seems that ReactApplicationContext cannot access Android assets folder.
Could anybody tell me ho to do ?
Or, is there any workaround?
Thank you.
public class Predictor extends ReactContextBaseJavaModule {
private ReactApplicationContext mContext;
public Predictor(ReactApplicationContext reactContext) {
super(reactContext);
mContext = reactContext;
// how to access android assets folder?
String ss = mContext.getCacheDir().toString();
}

Related

React Native how to use Braintree PayPal Value

I use react-native to develop an app, and I need to connect to braintree (paypal value). The official provides 3 kinds of sdk, js, android, and ios. I try to connect to the native android library, but it doesn't seem to have any effect.After calling the native method in ReactNative, only "react-native-test" is output (No error is reported, and there is no change on the interface. It seems that it should jump to PayPal authorization to be normal). I'm not sure if it's my problem . I also tried to use js library in react-native, but after importing a certain method, my program doesn't start. Or can I only do it in webView? Has anyone connected with PayPal value? Can you give me some advice? Thanks.
Here is the documentation I refer to.
Below is my android code:
public class BraintreeValueModule extends ReactContextBaseJavaModule implements PayPalListener {
private static ReactApplicationContext reactContext;
private Callback successCallback;
private Callback errorCallback;
private BraintreeClient braintreeClient;
private PayPalClient payPalClient;
public BraintreeValueModule(ReactApplicationContext context) {
super(context);
reactContext = context;
}
#NonNull
#Override
public String getName() {
return "BraintreeValueModule";
}
#ReactMethod
public void test(){
System.out.println("react-native-test");
braintreeClient = new BraintreeClient(reactContext.getApplicationContext(), "sandbox_ykbznr4s_ctmssyj6wz2qcj2g");
FragmentActivity activity = (FragmentActivity) getCurrentActivity();
activity.runOnUiThread(new MyRunnable(activity,braintreeClient));
//The following writing method will report an error: Method addObserver must be called on the main thread
//if(activity != null){
// payPalClient = new PayPalClient(activity, braintreeClient);
// payPalClient.setListener(this);
//}
}
#Override
public void onPayPalSuccess(#NonNull PayPalAccountNonce payPalAccountNonce) {
successCallback.invoke(payPalAccountNonce.toString());
}
#Override
public void onPayPalFailure(#NonNull Exception error) {
if (error instanceof UserCanceledException) {
// user canceled
errorCallback.invoke("use canceled");
} else {
// handle error
errorCallback.invoke("error");
}
}
}
public class MyRunnable implements Runnable, PayPalListener {
private BraintreeClient braintreeClient;
private PayPalClient payPalClient;
private FragmentActivity activity;
MyRunnable(FragmentActivity activity,BraintreeClient braintreeClient){
this.activity = activity;
this.braintreeClient = braintreeClient;
}
#Override
public void run() {
if(activity != null){
payPalClient = new PayPalClient(activity, braintreeClient);
payPalClient.setListener(this);
}
}
#Override
public void onPayPalSuccess(#NonNull PayPalAccountNonce payPalAccountNonce) {
System.out.println(payPalAccountNonce.getString());
}
#Override
public void onPayPalFailure(#NonNull Exception error) {
if (error instanceof UserCanceledException) {
// user canceled
System.out.println("use canceled");
} else {
// handle error
System.out.println("error");
}
}
}
I believe you might have missed out the following few lines:
PayPalVaultRequest request = new PayPalVaultRequest();
request.setBillingAgreementDescription("Your agreement description");
payPalClient.tokenizePayPalAccount(getCurrentActivity(), request);
However, I believe it might still not work as I had similar problem when I was trying to integrate the drop-in.
I'm afraid that you'll need to initialise your clients (in your case, BraintreeClient and PaypalClient) in the onCreate method of your MainActivity.
And then try to call a reference to the client (either by SharedPreference or static variable) in your module to launch the drop in.
Pretty sure it's similar issue to your case.
This only applies to the v4 library.
Read more on this thread:
https://github.com/braintree/braintree-android-drop-in/issues/374#issuecomment-1345929549

Cling: Looking for Opensource code for uPnp ContentDirectory

I'm trying to build a uPnP control point for controlling audio and I am using Java the cling library. To browse the music on the server requires the ContentDirectory service, cling provides the api to access this but doesnt provide any classes to represent the various actions and arguments requiring me to write lots of boilerplate code, I wonder does such a library exist ?
For example Ive create a Browse class for the Browse action of a Content Directory
import org.fourthline.cling.model.meta.Action;
import org.fourthline.cling.model.types.UnsignedIntegerFourBytes;
public class Browse extends AbstractActionAndInvocation
{
//INPUT
public static final String OBJECT_ID = "ObjectID";
public static final String BROWSE_FLAG = "BrowseFlag";
public static final String FILTER = "Filter";
public static final String STARTING_INDEX = "StartingIndex";
public static final String REQUESTED_COUNMT = "RequestedCount";
public void setObjectID(String objectID)
{
actionInvocation.setInput(OBJECT_ID, objectID);
}
public void setBrowseFlag(BrowseFlag browseFlag)
{
actionInvocation.setInput(BROWSE_FLAG, browseFlag.getParameterName());
}
public void setFilter(String filter)
{
actionInvocation.setInput(FILTER, filter);
}
public void setStartingIndex(int startingIndex)
{
actionInvocation.setInput(STARTING_INDEX, new UnsignedIntegerFourBytes(startingIndex));
}
public void setRequestedCount(int requestCount)
{
actionInvocation.setInput(REQUESTED_COUNMT, new UnsignedIntegerFourBytes(requestCount));
}
public Browse(Action action)
{
super(action);
}
}
Since ContentDirectory only has a predefined list of Actions it seems weird that these don't already exist somewhere ?
Within the cling-support module there are useful classes such as callback classes for the main services
e.g
org.fourthline.cling.support.contentdirectory.callback.Browse.java;
However I found them to be of limited usefulness and serve more as an example implementation rather than one that can be used as is.

Force Android App Link to open in browser

We have a React Native app which shows our mobile website and adds some extra features.
Since Android 12 App links (like domain.com) always open our app: https://developer.android.com/training/app-links
This behaviour is not always desirable, for example in this scenario:
Customer is logged in and starts an order via their browser
Customer needs to pay via an app from their bank
After payment, the customer is redirected back to our website (domain.com/returnUrl)
Now the app is opened, instead of the browser, so the customer isn't logged-in and isn't allowed to view the page.
In this case, after payment started from the browser, we would like to redirect the customer back to the browser instead of the app.
Is there a way to open a link in the browser (ie. via domain.com/returnUrl?force-browser) instead of the app?
Related: Android App link - Open a url from app in browser without triggering App Link
Based on this answer, I've created a RN Native Module and instead of using await Linking.openURL(url) you can just use the Native Module's exposed method to open Android App links.
I've followed the official RN tutorial to make an Android Native Module.
So in summary, first you will have to create a Java class file inside android/app/src/main/java/com/your-app-name/folder. I've named the module DefaultBrowserModule so the path is src/main/java/com/your-app-name/DefaultBrowserModule.java. Here's how it looks like:
package com.your-app-name;
import android.content.Intent;
import android.net.Uri;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
public class DefaultBrowserModule extends ReactContextBaseJavaModule {
private ReactApplicationContext _context;
DefaultBrowserModule(ReactApplicationContext context) {
super(context);
this._context = context;
}
#NonNull
#Override
public String getName() {
return "DefaultBrowserModule";
}
// This is the method that we're exposing
#ReactMethod
public void openUrl(String url) {
Intent defaultBrowser = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, Intent.CATEGORY_APP_BROWSER);
defaultBrowser.setData(Uri.parse(url));
// Through ReactApplicationContext's current activty, start a new activity
this._context.getCurrentActivity().startActivity(defaultBrowser);
}
}
After that we'll have to register the module with React Native. That can be done by adding a new Java class file to the android/app/src/main/java/com/your-app-name/ folder. I've named mine DefaultBrowserPackage: src/main/java/com/your-app-name/DefaultBrowserPackage.java:
package com.your-app-name;
import androidx.annotation.NonNull;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class DefaultBrowserPackage implements ReactPackage {
#NonNull
#Override
public List<NativeModule> createNativeModules(#NonNull ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new DefaultBrowserModule(reactContext));
return modules;
}
#NonNull
#Override
public List<ViewManager> createViewManagers(#NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
The last step is to register the DefaultBrowserPackage inside of MainApplication.java (android/app/src/main/java/com/your-app-name/MainApplication.java). Locate ReactNativeHost’s getPackages() method and add your package to the packages list
#Override
protected List<ReactPackage> getPackages() {
#SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// below DefaultBrowserPackage is added to the list of packages returned
packages.add(new DefaultBrowserPackage());
return packages;
}
Now we are ready to use it inside of JS. So wherever you want to use it, you can do it like this:
import { Linking, NativeModules, Platform } from 'react-native';
// DefaultBrowserModule should be equal to the return value of the getName() method
// inside of the src/main/java/com/your-app-name/DefaultBrowserModule.java class
const { DefaultBrowserModule } = NativeModules;
export const openUrl = async (url) => {
if (Platform.OS === 'android') {
DefaultBrowserModule.openUrl(url);
} else {
await Linking.openURL(url);
}
};
// And then use it like this
await openUrl('https://my-app-link-domain.com');
Deep and universal linking happens on the operating level and it's hard to control the behavior of other app linking I think it should security breach as some apps try to override the deep link behaviors of another app.
Try to create your simple page with your custom URL https://my-domain.com which redirect to tour target URL without opening associated app.
The best possible solution for that can be using android:pathPattern in android manifest. Basically you have to provide path pattern (a sort regex) to match the valid links.
Documentation for that can be found here.
https://developer.android.com/guide/topics/manifest/data-element

Where to put and how to read from manually created DB file using Xamarin forms?

I created questions.db file using DB Browser.
Here it is: My lovely .db file
I want to add it to Xamarin.Forms solution and read data from it. (This is going to be quiz app)
That sounds pretty simple, right, but I'm stuck googling for hours. Most answers just link to this https://learn.microsoft.com/en-us/xamarin/xamarin-forms/data-cloud/data/databases
Which explains nothing about the manually created database file, where to put it etc.
EDIT: this is what I'm trying to achieve: https://arteksoftware.com/deploying-a-database-file-with-a-xamarin-forms-app/
Sadly, the guide is outdated and many things of it doesn't work.
So where do I put my .db file? In MyApp.Android Assets and Resources for MyApp.iOS? If so, how do I get DBpath string then for new connection?
_database = new SQLiteAsyncConnection(dbPath);
If what you want to do is:
Add a pre existing database to your project and load that database to be used by your App then keep on reading.
Short Answer
You need to add your database file to a specific folder in the paltform project (Assets in Android, in iOS you can simply create a folder and put the file there) and use DependencyService to access it. Copy the database file to somewhere in your device (App data folder or InternalStorage or watever is allowed on each platform) and use that final path in the SQLite constructor. From now on, you can simply use that SQLite connection as any other to perform CRUD operation on your Database.
Long Answer
I will now describe how to achieve this step by step from creating a new Xamarin.Forms project to reading data from the preloaded database.
Note: the follwoing solution will only cover the Android part of the CrossPlatform solution. Extending this to iOS should be no problem.
Disclaimer: The code presented next should just be taken as a guideline and by no means I suggest that is proved or production like.
0. Getting project ready:
Create a new Xamarin.Forms project (on my case i use Visual Studio v. 16.3.8 and the new project has Xamarin.Forms 4.2 installed)
Then, first of all, install sqlite nuget package in ALL your projects:
1. Prepare your Database
In App.xaml.cs file
// Create db static property to perform the
// Database calls from around the project
public static Database db { get; set; }
public App()
{
InitializeComponent();
MainPage = new MainPage();
}
Create a new class file and name it Database.cs. There you open the database connection.
public class Database
{
// Create a SQLiteAsyncConnection property to be accessed
// publicly thru your App.
public SQLiteAsyncConnection DBInstance { get; set; }
public Database(String databasePath)
{
DBInstance = new SQLiteAsyncConnection(databasePath);
}
private async Task<string> GetDatabaseFilePath()
{
return await DependencyService.Get<IPathFinder>().GetDBPath();
}
}
So far so good, but... What should actually be the path of your preloaded database?
Well, that is the next part.
2. Load pre-existent database to your project and get a path to it (Android)
So the big question is where to store your pre-existent database in the project.
One option is to add it as an Asset. To implement this approach, do the following:
2.1 Add database file to Assets folder in Android project
Now we want to access that database. To do so, follow the next steps:
2.2 Create an Interface IPathFinder in your App project
And there define a single member GetDBPath()
public interface IPathFinder
{
Task<String> GetDBPath();
}
Now, in your Android project create a class file PathFinder.cs to implement this interface
and implement the method (Note the Dependency Attribute above the namespace!)
using System;
using SysIO = System.IO;
using System.Threading.Tasks;
using Java.IO;
using Xamarin.Forms;
using TestDB.Droid;
[assembly: Dependency(typeof(PathFinder))]
namespace TestDB.Droid
{
public class PathFinder : IPathFinder
{
public async Task<string> GetDBPath()
{
String dbPath = String.Empty;
if (await PermissonManager.GetPermission(PermissonManager.PermissionsIdentifier.Storage))
{
String systemPath = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.RootDirectory.Path).Path;
String tempFolderPath = SysIO::Path.Combine(systemPath, "MytestDBFolder");
if (!SysIO::File.Exists(tempFolderPath))
{
new File(tempFolderPath).Mkdirs();
}
dbPath = SysIO::Path.Combine(tempFolderPath, "test.db");
if (!SysIO::File.Exists(dbPath))
{
Byte[] dbArray;
using (var memoryStream = new SysIO::MemoryStream())
{
var dbAsset = MainActivity.assets.Open("test.db");
dbAsset.CopyTo(memoryStream);
dbArray = memoryStream.ToArray();
}
SysIO.File.WriteAllBytes(dbPath, dbArray);
}
}
return dbPath;
}
}
}
In the GetDBPath() method, the first to note is the GetPermission method. This is needed since Android API 23 in order to manage the App permissions.
Create a file called PermissonManager in your Android project
And add the code below
using System;
using System.Threading.Tasks;
using Android;
using Android.App;
using Android.Content.PM;
using Android.OS;
using Android.Support.V4.App;
namespace TestDB.Droid
{
public class PermissonManager
{
public enum PermissionsIdentifier
{
Storage // Here you can add more identifiers.
}
private static String[] GetPermissionsRequired(PermissionsIdentifier identifier)
{
String[] permissions = null;
if (identifier == PermissionsIdentifier.Storage)
permissions = PermissionExternalStorage;
return permissions;
}
private static Int32 GetRequestId(PermissionsIdentifier identifier)
{
Int32 requestId = -1;
if (identifier == PermissionsIdentifier.Storage)
requestId = ExternalStorageRequestId;
return requestId;
}
public static TaskCompletionSource<Boolean> PermissionTCS;
public static readonly String[] PermissionExternalStorage = new String[] { Manifest.Permission.ReadExternalStorage, Manifest.Permission.WriteExternalStorage };
public const Int32 ExternalStorageRequestId = 2;
public static async Task<Boolean> GetPermission(PermissionsIdentifier identifier)
{
Boolean isPermitted = false;
if ((Int32)Build.VERSION.SdkInt < 23)
isPermitted = true;
else
isPermitted = await GetPermissionOnSdk23OrAbove(GetPermissionsRequired(identifier), GetRequestId(identifier));
return isPermitted;
}
private static Task<Boolean> GetPermissionOnSdk23OrAbove(String[] permissions, Int32 requestId)
{
PermissionTCS = new TaskCompletionSource<Boolean>();
if (MainApplication.CurrentContext.CheckSelfPermission(permissions[0]) == (Int32)Permission.Granted)
PermissionTCS.SetResult(true);
else
ActivityCompat.RequestPermissions((Activity)MainApplication.CurrentContext, permissions, requestId);
return PermissionTCS.Task;
}
public static void OnRequestPermissionsResult(Permission[] grantResults)
{
PermissionTCS.SetResult(grantResults[0] == Permission.Granted);
}
}
}
In that class you note the presence of MainApplication, which provides the CurrentContext. You will also have to add that class file
and there add the following code
using System;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
namespace DemoDB.Droid
{
[Application]
public partial class MainApplication : Application, Application.IActivityLifecycleCallbacks
{
private static Context _currentContext = Application.Context;
internal static Context CurrentContext
{
get => _currentContext;
private set
{
_currentContext = value;
}
}
internal static String FileProviderAuthority
{
get => MainApplication.CurrentContext.ApplicationContext.PackageName + ".fileprovider";
}
public MainApplication(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer)
{
}
public override void OnCreate()
{
base.OnCreate();
RegisterActivityLifecycleCallbacks(this);
}
public override void OnTerminate()
{
base.OnTerminate();
UnregisterActivityLifecycleCallbacks(this);
}
public void OnActivityCreated(Activity activity, Bundle savedInstanceState)
{
CurrentContext = activity;
}
public void OnActivityDestroyed(Activity activity)
{
}
public void OnActivityPaused(Activity activity)
{
}
public void OnActivityResumed(Activity activity)
{
CurrentContext = activity;
}
public void OnActivitySaveInstanceState(Activity activity, Bundle outState)
{
}
public void OnActivityStarted(Activity activity)
{
CurrentContext = activity;
}
public void OnActivityStopped(Activity activity)
{
}
}
}
Then your PermissonManager is almost ready. Now you just have to override OnRequestPermissionsResult in the MainActivity file
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
PermissonManager.OnRequestPermissionsResult(grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
Getting back to the GetPath() method, you see a mysterious MainActivity.assets property call. This has to be created in the MainActivity as follows
public static AssetManager assets;
protected override void OnCreate(Bundle savedInstanceState)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
assets = this.Assets;
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
LoadApplication(new App());
}
Almost ready! Now you just call your database.
3. Use your database!
From the OnAppearing of the Main page, make a simple call to the database, to create it and access it.
protected override async void OnAppearing()
{
String databasePath = await Database.GetDatabaseFilePath();
App.db = new Database(databasePath);
var table = await App.db.DBInstance.CreateTableAsync<Category>();
// here Category is a class that models the objects
// present in my pre-existent database
List<Category> categories = new List<Category>();
categories = await App.db.DBInstance.Table<Category>().ToListAsync();
base.OnAppearing();
}
And that is it.
I hope this is helpful :P

NullPointerException on Google Banner Ads

my app get crashing frequently due to NullPointerException and Native crash and thus I am losing my play store rank day by day. someone please help me on this.. is there any way if banner ad is not ready, can I show native ad (optional question)?
NullPointerException at: mAdView.loadAd(new AdRequest.Builder().build());
My app build version 26.0.1 and using Android Studio 3.0 Beta 2.
my code:
public class B1 extends Fragment {
public B1() {
// Required empty public constructor
}
private AdView mAdView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_b1, container, false);
//Banner
mAdView = rootView.findViewById(R.id.adView);
mAdView.loadAd(new AdRequest.Builder().build());
mAdView.setAdListener(new AdListener() {
#Override
public void onAdLoaded() {
mAdView.setVisibility(View.VISIBLE);
}
#Override
public void onAdFailedToLoad(int error) {
mAdView.setVisibility(View.GONE);
}
});
return rootView;
}
}
The banner is ready. Your findViewById is returning null. Make sure rootView actually contains the AdView R.id.adView