Following this guide https://developers.google.com/admob/ump/android/quick-start I tried to add everything to my app.
What I did:
link funding choices to admob
added ump to build.gradle
added the app ID to android manifest
set up a dialog for the app in admob and activated it for the app
Then I added this code to my app
ConsentRequestParameters params = new ConsentRequestParameters
.Builder()
.setTagForUnderAgeOfConsent(false)
.build();
consentInformation = UserMessagingPlatform.getConsentInformation(this);
consentInformation.requestConsentInfoUpdate(
this,
params,
new ConsentInformation.OnConsentInfoUpdateSuccessListener() {
#Override
public void onConsentInfoUpdateSuccess() {
// The consent information state was updated.
// You are now ready to check if a form is available.
if (consentInformation.isConsentFormAvailable()) {
loadForm();
}
else {
Context context = getApplicationContext();
CharSequence toastText = "No Form Available";
int duration = Toast.LENGTH_LONG;
Toast toast = Toast.makeText(context, toastText, duration);
toast.show();
}
}
},
new ConsentInformation.OnConsentInfoUpdateFailureListener() {
#Override
public void onConsentInfoUpdateFailure(FormError formError) {
// Handle the error.
Context context = getApplicationContext();
CharSequence toastText = "Error";
int duration = Toast.LENGTH_LONG;
Toast toast = Toast.makeText(context, toastText, duration);
toast.show();
}
});
and
public void loadForm() {
UserMessagingPlatform.loadConsentForm(
this,
new UserMessagingPlatform.OnConsentFormLoadSuccessListener() {
#Override
public void onConsentFormLoadSuccess(ConsentForm consentForm) {
MainActivity.this.consentForm = consentForm;
if(consentInformation.getConsentStatus() == ConsentInformation.ConsentStatus.REQUIRED) {
consentForm.show(
MainActivity.this,
new ConsentForm.OnConsentFormDismissedListener() {
#Override
public void onConsentFormDismissed(#Nullable FormError formError) {
// Handle dismissal by reloading form.
loadForm();
}
});
}
}
},
new UserMessagingPlatform.OnConsentFormLoadFailureListener() {
#Override
public void onConsentFormLoadFailure(FormError formError) {
// Handle the error
}
}
);
}
However, I always end up getting the toast "Error" landing in onConsentInfoUpdateFailure(FormError formError) independet from testing on my mobile phone or in the virtual device (I am in europe btw).
Am I missing something?
Thanks,
Celdri
I had the same issue and I solved it just by configuring properly my adMob account.
Related
From the Flutter side, using the PlatformChannel, I am navigating to an Android Java activity, and doing some processes.
The activity successfully opens and I'm able to do the functionality and have the final result of it.
How may I navigate back to the Flutter side to a specific page and pass a value?
P.S.: without going back to the same page and then redirecting to the
next page.
On the Flutter side:
I have these variables
/// Filters Method Channel
final filtersChannel = const MethodChannel('flutter.native/filters');
/// Filters Method Channel
final filtersResultChannel = const MethodChannel("flutter.native/result_filters");
I have a floatingActionButton with this function which invokes a MethodChannel
Future<void> startNewActivity() async {
try {
await filtersChannel.invokeMethod('open_filters');
} on PlatformException catch (e) {
debugPrint("Failed to Invoke: '${e.message}'.");
}
}
On the MainActivity.java
On the protected void onCreate(#Nullable Bundle savedInstanceState) function, I'm starting an activity which has the AR video recording like this:
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(this, FiltersActivity.class);
startActivity(intent);
}
On the FiltersActivity.java
On the public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) function
I’m defining and invoking my two channels:
The flutter.native/result_filters channel which builds the UI and
the functionality.
The flutter.native/filters channel which returns the final result.
Here:
#Override
public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
String resultFiltersChannelIdentifier = "flutter.native/result_filters";
filtersResultChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), resultFiltersChannelIdentifier);
String filtersChannelIdentifier = "flutter.native/filters";
MethodChannel filtersChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), filtersChannelIdentifier);
filtersChannel.setMethodCallHandler(this::filtersMethodCallHandler);
}
Then, the flutter.native/filters displays the UI using the filtersMethodCallHandler function. Here:
private void filtersMethodCallHandler(MethodCall methodCall, MethodChannel.Result result) {
if (methodCall.method.equals("open_filters")) {
openUI();
} else {
result.notImplemented();
}
}
In the openUI function, I'm assigning the record button a function, here:
recordButton.setOnClickListener(this::toggleRecording);
And here's the toggleRecording function:
public void toggleRecording(View unusedView) {
boolean recording = videoRecorder.onToggleRecord();
if (recording) {
recordButton.setImageResource(R.drawable.round_stop);
Toast.makeText(this, "Started Recording", Toast.LENGTH_SHORT).show();
} else {
recordButton.setImageResource(R.drawable.round_videocam);
Toast.makeText(this, "Recording Stopped", Toast.LENGTH_SHORT).show();
videoPath = videoRecorder.getVideoPath().getAbsolutePath();
Toast.makeText(this, "Video saved: " + videoPath, Toast.LENGTH_SHORT).show();
Log.d(TAG, "Video saved: " + videoPath);
// Send notification of updated content.
ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.TITLE, "Sceneform Video");
values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
values.put(MediaStore.Video.Media.DATA, videoPath);
getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
filtersResultChannel.invokeMethod("filters_result", videoPath);
finish();
}
}
As shown above, I'm invoking the filters_result method for the filtersResultChannel channel and I'm adding the videoPath to it.
And then, I'm calling the finish(); method to close the FiltersActivity and return back to the MainAvtivity which successfully returns me to the Flutter page!
BACK to the Flutter side,
I'm listening to the filtersResultChannel like this:
#override
void initState() {
super.initState();
filtersResultChannel.setMethodCallHandler(_filtersResultHandler);
}
Future _filtersResultHandler(MethodCall methodCall) async {
if (methodCall.method == "filters_result") {
final videoPath = methodCall.arguments;
if (videoPath != null && videoPath.length >= 0) {
SchedulerBinding.instance.addPostFrameCallback((_) {
debugPrint("YES YES YES => $videoPath");
setState(() {
reportStatus = videoPath;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => VideoShow(clipPath: videoPath),
),
);
});
});
}
return null;
} else {
return null;
}
}
As shown above, I have a debugPrint statement, this statement prints the returned videoPath from the filtersResultChannel
<--------->
THE PROBLEM
<--------->
Even though I'm successfully getting the videoPath value and successfully returning back to the Flutter page, I'm NOT able to use it!!
The setState(); doesn't update the UI NOR navigate to the next screen, the VideoShow screen!
HOW MAY I FIX SUCH AN ISSUE?
My requirement is that I have to built a video recording app in which user will save his 30 seconds of video which will be sent to the another server. User will use the video recording only for once.
How can I store that 30 second recorded video in SQLite database as I cannot store it in gallery as it is confidential.
Any help in this topic is highly appreciated.
Below is my demo app in which I'm just saving it into the gallery for now.
public class VideoKycActivity extends AppCompatActivity implements ImageAnalysis.Analyzer, View.OnClickListener {
private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
PreviewView previewView;
private VideoCapture videoCapture;
private Button bRecord;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
previewView = findViewById(R.id.previewView);
bRecord = findViewById(R.id.bRecord);
bRecord.setText("start recording"); // Set the initial text of the button
bRecord.setOnClickListener(this);
cameraProviderFuture = ProcessCameraProvider.getInstance(this);
cameraProviderFuture.addListener(() -> {
try {
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
startCameraX(cameraProvider);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}, getExecutor());
}
Executor getExecutor() {
return ContextCompat.getMainExecutor(this);
}
#SuppressLint("RestrictedApi")
private void startCameraX(ProcessCameraProvider cameraProvider) {
cameraProvider.unbindAll();
CameraSelector cameraSelector = new CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build();
Preview preview = new Preview.Builder()
.build();
preview.setSurfaceProvider(previewView.getSurfaceProvider());
// Video capture use case
videoCapture = new VideoCapture.Builder()
.setVideoFrameRate(30)
.build();
// Image analysis use case
ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build();
imageAnalysis.setAnalyzer(getExecutor(), this);
//bind to lifecycle:
cameraProvider.bindToLifecycle((LifecycleOwner) this, cameraSelector, preview, videoCapture);
}
#Override
public void analyze(#NonNull ImageProxy image) {
// image processing here for the current frame
Log.d("TAG", "analyze: got the frame at: " + image.getImageInfo().getTimestamp());
image.close();
}
#SuppressLint("RestrictedApi")
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.bRecord:
if (bRecord.getText() == "start recording"){
bRecord.setText("stop recording");
recordVideo();
} else {
bRecord.setText("start recording");
videoCapture.stopRecording();
}
break;
}
}
#SuppressLint("RestrictedApi")
private void recordVideo() {
if (videoCapture != null) {
long timestamp = System.currentTimeMillis();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, timestamp);
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4");
try {
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
videoCapture.startRecording(
new VideoCapture.OutputFileOptions.Builder(
getContentResolver(),
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
contentValues
).build(),
getExecutor(),
new VideoCapture.OnVideoSavedCallback() {
#Override
public void onVideoSaved(#NonNull VideoCapture.OutputFileResults outputFileResults) {
Toast.makeText(VideoKycActivity.this, "Video has been saved successfully.", Toast.LENGTH_SHORT).show();
}
#Override
public void onError(int videoCaptureError, #NonNull String message, #Nullable Throwable cause) {
Toast.makeText(VideoKycActivity.this, "Error saving video: " + message, Toast.LENGTH_SHORT).show();
}
}
);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
First of all SQL(Room in our case) database is not the best approach to storing a single element. I suggest you use PharedPreferences. And about your main question. Room so as SharedPreferences working with primary types (to be honest Room store data in SQL types like TEXT, INTEGER, BLOB, REAL and UNDEFINED and that convert in java types) so for your goal, you have to convert your file to something that feet for our storage. I guess you can use a String.
You could convert the file to a Base64 format and save it as a String however depending on your file size you could face problems with memory because the Base64 format consumes more memory than the initial file. Here is an explanation of Base64 format memory consumption.
https://stackoverflow.com/a/27798343/5422725
I'm migrating an app from the play store to the Amazon App store, considering that in this way it will be avilable for Windows 11 too.
For this to make something very fast and easy i made an activity called PurchaseActivity which contains the codes brought by the amazon IAP guide PDF.
The activity is called from a "buy now" button of a dialog window with the following code:
public class PurchaseActivity extends Activity {
String parentSKU = "com.amazon.sample.iap.subscription.mymagazine";
//Define UserId and MarketPlace
private String currentUserId;
private String currentMarketplace;
private ProgressDialog progress;
#Override
protected void onStart(){
super.onStart();
progress = new ProgressDialog(this);
progress.setTitle("Purchasing");
progress.setMessage("Wait while making the purchase...");
progress.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
progress.dismiss();//dismiss dialog
finish();
}
});
progress.setCancelable(false); // disable dismiss by tapping outside of the dialog
progress.show();
PurchasingService.registerListener(this, purchasingListener);
PurchasingService.purchase(parentSKU);
}
#Override
protected void onResume() {
super.onResume();
//getUserData() will query the Appstore for the Users information
PurchasingService.getUserData();
//getPurchaseUpdates() will query the Appstore for any previous purchase
PurchasingService.getPurchaseUpdates(true);
//getProductData will validate the SKUs with Amazon Appstore
final Set<String> productSkus = new HashSet<String>();
productSkus.add(parentSKU);
PurchasingService.getProductData(productSkus);
Log.v("Validating SKUs", "Validating SKUs with Amazon");
}
PurchasingListener purchasingListener = new PurchasingListener() {
#Override
public void onUserDataResponse(UserDataResponse response) {
final UserDataResponse.RequestStatus status = response.getRequestStatus();
switch (status) {
case SUCCESSFUL:
currentUserId = response.getUserData().getUserId();
currentMarketplace = response.getUserData().getMarketplace();
Log.v("IAP SDK", "loaded userdataResponse");
break;
case FAILED:
case NOT_SUPPORTED:
// Fail gracefully.
Log.v("IAP SDK", "loading failed");
break;
}
}
#Override
public void onProductDataResponse(ProductDataResponse productDataResponse) {
switch (productDataResponse.getRequestStatus()) {
case SUCCESSFUL:
//get informations for all IAP Items (parent SKUs)
final Map<String, Product> products = productDataResponse.getProductData();
for (String key : products.keySet()) {
Product product = products.get(key);
Log.v("Product:", String.format("Product: %s\n Type: %s\n SKU: %s\n Price: %s\n Description: %s\n", product.getTitle(), product.getProductType(),
product.getSku(), product.getPrice(), product.getDescription()));
}
//get all unavailable SKUs
for (String s : productDataResponse.getUnavailableSkus()) {
Log.v("Unavailable SKU:" + s, "Unavailable SKU:" + s);
}
break;
case FAILED:
Log.v("FAILED", "FAILED");
progress.dismiss();
finish();
break;
}
}
#Override
public void onPurchaseResponse(PurchaseResponse purchaseResponse) {
switch (purchaseResponse.getRequestStatus()) {
case SUCCESSFUL:
PurchasingService.notifyFulfillment(purchaseResponse.getReceipt().getReceiptId(),
FulfillmentResult.FULFILLED);
break;
case FAILED:
progress.dismiss();
finish();
break;
}
}
#Override
public void onPurchaseUpdatesResponse(PurchaseUpdatesResponse response) {
// Process receipts
switch (response.getRequestStatus()) {
case SUCCESSFUL:
for (final Receipt receipt : response.getReceipts()) {
// Process receipts
if (!receipt.isCanceled()) {
// sharedprefs
SharedPreferences sharedPreference = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
SharedPreferences.Editor sharedPrefEditor = sharedPreference.edit();
sharedPrefEditor.putBoolean("isPro",true);
sharedPrefEditor.apply();
progress.dismiss();
finish();
}
}
if (response.hasMore()) {
PurchasingService.getPurchaseUpdates(true);
}
break;
case FAILED:
Log.d("FAILED", "FAILED");
progress.dismiss();
finish();
break;
}
}
};
}
Yeah i know i should not call all that stuff in the onStart() method but i'll make an onCreate() with a UI later.
As you can see from this code, i'm testing in sandbox mode.
THE PROBLEM: Actually when the activity starts, i see progressDialog, and i read in the debug logs that "V/Validating SKUs: Validating SKUs with Amazon" but i don't see then the amazon buy window. It also seems like the listener code is never called, even if i put some breakpoints in there, they're never reached which is very weird considering that apprently it gets initialized and called successfully by the method "PurchasingService.registerListener(this, purchasingListener)"
Any help would be very apreciated!
Thanks and have a good evening
You need to call registerListener in your Application's onCreate, otherwise Amazon fails to detect there's an Activity being displayed and it will not show the purchase dialog.
I have this app which parses SMS and then converts them into Audio. My app users usually minimize the app and runs it all the time. But my app is getting terminated after sometime. How can i make sure my app will run till a user "terminates" it. Since the core functionality of the app is to convert SMS to audio, i need it running all the time.How can i do this ?
My current MainActivity.java
public class MainActivity extends AppCompatActivity {
TextView txtGateway, txtTime, txtAmount;
Speakerbox speakerbox;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//textView = findViewById(R.id.txt_message);
speakerbox = new Speakerbox(getApplication());
txtAmount = findViewById(R.id.tv_amount);
txtGateway = findViewById(R.id.tv_gateway);
txtTime = findViewById(R.id.tv_time);
requestSmsPermission();
}
#Override
public void onResume() {
LocalBroadcastManager.getInstance(this).registerReceiver(receiver, new IntentFilter("otp"));
super.onResume();
}
#Override
public void onPause() {
LocalBroadcastManager.getInstance(this).registerReceiver(receiver, new IntentFilter("otp"));
super.onPause();
}
private BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equalsIgnoreCase("otp")) {
final String message = intent.getStringExtra("message");
String gateway = intent.getStringExtra("gateway");
String time = intent.getStringExtra("time");
String amount = intent.getStringExtra("amount");
speakerbox.play(message);
txtGateway.setText(gateway);
txtTime.setText(time);
txtAmount.setText(amount);
// message is the fetching OTP
}
}
};
/**
* Requesting multiple permissions (storage and location) at once
* This uses multiple permission model from dexter
* On permanent denial opens settings dialog
*/
private void requestSmsPermission() {
Dexter.withActivity(this)
.withPermissions(
Manifest.permission.RECEIVE_SMS,
Manifest.permission.READ_SMS,
Manifest.permission.SEND_SMS,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
.withListener(new MultiplePermissionsListener() {
#Override
public void onPermissionsChecked(MultiplePermissionsReport report) {
// check if all permissions are granted
if (report.areAllPermissionsGranted()) {
// Toast.makeText(getApplicationContext(), "All permissions are granted!", Toast.LENGTH_SHORT).show();
}
// check for permanent denial of any permission
if (report.isAnyPermissionPermanentlyDenied()) {
// show alert dialog navigating to Settings
showSettingsDialog();
}
}
#Override
public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) {
token.continuePermissionRequest();
}
}).
withErrorListener(new PermissionRequestErrorListener() {
#Override
public void onError(DexterError error) {
Toast.makeText(getApplicationContext(), "Error occurred! ", Toast.LENGTH_SHORT).show();
}
})
.onSameThread()
.check();
}
/**
* Showing Alert Dialog with Settings option
* Navigates user to app settings
* NOTE: Keep proper title and message depending on your app
*/
private void showSettingsDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("Need Permissions");
builder.setMessage("This app needs permission to use this feature. You can grant them in app settings.");
builder.setPositiveButton("GOTO SETTINGS", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
openSettings();
}
});
builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
builder.show();
}
// navigating user to app settings
private void openSettings() {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivityForResult(intent, 101);
}
}
Use a foreground service to ensure that your app is not killed by Android. On newer version of Android, app's background process gets killed after sometime. Having a foreground service will ensure that your app stays active. Keep the service on background thread and not on the main thread.
Read more at:
https://developer.android.com/guide/components/services
and
https://androidwave.com/foreground-service-android-example/
Activities are used for user facing parts of your application. Use service instead. Most likely, your application is getting terminated by os due to low memory situation.
Use service for the core functionality if you want to run all the time in background.If user is not using your app actively yet app is taking RAM memory then OS will terminate the app to avoid out of memory.
When my app try to share something, the description is shown in the share activity, but it does not show up when it is posted. I went through many stackoverflow posts, but nothing could solve my problem.
Here is my call flow :
Share button is clicked
Activity calls a static method and passes itself and content to share to it via Bundle
Share activity is invoked from this static method. It displays the content to share correctly with all image, caption and description
Content is shared successfully
When the facebook post is checked, it just shows the playstore app details, with the image that I had set in 3, without the description
Here is the code that i use
if (FacebookDialog.canPresentShareDialog(activity.getApplicationContext(),
FacebookDialog.ShareDialogFeature.SHARE_DIALOG))
{
FacebookDialog shareDialog = new FacebookDialog.ShareDialogBuilder(activity)
.setLink("<playstore link>")
.setApplicationName("<appname>")
.setName("<some name>")
.setCaption("<some text>")
.setDescription("a description")
.setPicture("http://url/to/image.jpg")
.build();
uiHelper.trackPendingDialogCall(shareDialog.present());
}
But sharing the same using FeedDialog works
Bundle params = new Bundle();
params.putString("name", "name");
params.putString("caption", "caption");
params.putString("description","desc");
params.putString("link", "playstore link");
params.putString("picture", "http://url/to/image.jpg");
WebDialog feedDialog = (
new WebDialog.FeedDialogBuilder(activity,
session,
params))
.setOnCompleteListener(new OnCompleteListener() {
#Override
public void onComplete(Bundle values,
FacebookException error) {
if (error == null) {
final String postId = values.getString("post_id");
if (postId != null) {
Toast.makeText(activity,
"Posted Successfully!",
Toast.LENGTH_SHORT).show();
activity.finish();
} else {
// User clicked the Cancel button
Toast.makeText(activity.getApplicationContext(),
"Publish cancelled",
Toast.LENGTH_SHORT).show();
activity.finish();
}
} else if (error instanceof FacebookOperationCanceledException) {
// User clicked the "x" button
Toast.makeText(activity.getApplicationContext(),
"Publish cancelled",
Toast.LENGTH_SHORT).show();
activity.finish();
} else {
Toast.makeText(activity.getApplicationContext(),
"An Error Occurred",
Toast.LENGTH_SHORT).show();
activity.finish();
}
}
})
.build();
feedDialog.show();
Thank You
From the docs (Versions 4.0+)
To show the ShareDialog for a link in your activity, create a
ShareDialog instance in your onCreate method:
public class MainActivity extends FragmentActivity {
CallbackManager callbackManager;
ShareDialog shareDialog;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FacebookSdk.sdkInitialize(getApplicationContext());
callbackManager = CallbackManager.Factory.create();
shareDialog = new ShareDialog(this);
// this part is optional
shareDialog.registerCallback(callbackManager, new FacebookCallback<Sharer.Result>() { ... });
}
Then show the ShareDialog:
if (ShareDialog.canShow(ShareLinkContent.class)) {
ShareLinkContent linkContent = new ShareLinkContent.Builder()
.setContentTitle("Hello Facebook")
.setContentDescription(
"The 'Hello Facebook' sample showcases simple Facebook integration")
.setContentUrl(Uri.parse("http://developers.facebook.com/android"))
.build();
shareDialog.show(linkContent);
}
Slartibartfast's Answer is working fine until compile 'com.facebook.android:facebook-android-sdk:4.24.0'
From Facebook SDK version 4.25.0
setContentTitle("") setContentDescription("") is depricated.
You have to use setQuote(""); Method instead of setContentDescription("");
For more detail about depreciation Refer: https://developers.facebook.com/docs/sharing/android