After migrating this next Java class:
public class ImportExportActivity extends BaseActivity
{
private String filename;
private static Intent oData;
private ActivityResultLauncher<Intent> activityResultLauncher;
private ActivityImportexportBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setImportDataActivityResultHandler();
}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
private void setImportDataActivityResultHandler(){
activityResultLauncher =
registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent data = result.getData();
if (data != null) {
oData = data;
boolean hasPermission =
TMPermission.verifyStorageReadPermissions((Activity)AppSettings.getContext());
appSettings.setRequestPermissionSource(RequestPermissionSource.IMPORTDATA);
if (hasPermission) {
String strResult = TMImport.importData(data);
TMToast toast = new TMToast(AppSettings.getContext());
toast.show(strResult, Toast.LENGTH_LONG);
}
}
}
});
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(appSettings.getRequestPermissionSource()
.equals(RequestPermissionSource.IMPORTDATA)){
if (TMPermission.hasReadPermission()) {
String strResult = TMImport.importData(oData);
TMToast toast = new TMToast(this);
toast.show(strResult, Toast.LENGTH_LONG);
}
}
}
public void setActivityLayout()
{
super.setActivityLayout();
setButtonsAppearance();
}
public void setContentView()
{
binding = DataBindingUtil.setContentView(this, R.layout.activity_importexport);
}
public void setActivityBackground(){
binding.llImportExportMain.setBackgroundColor(Color.parseColor(
appSettings.mainLayoutBackgroundColor
));
}
public void createActionBar()
{
String activityTitle = TMLocale.getStringResourceByName("activityrecords_textview_importexport").toUpperCase();
TMAppearance.createActionBar(this, activityTitle);
}
public void setFooterMenu()
{
FootermenuBinding footerBinding = binding.footermenu;
TMFooter.footerBinding = new WeakReference<>(footerBinding);
TMFooter.activity = new WeakReference<>(this);
TMFooter.dialog = new WeakReference<>(dialog);
TMFooter.setFooterMenu();
}
private void setButtonsAppearance()
{
configureButton(binding.btnImport);
configureButton(binding.btnExport);
setButtonClickListener(binding.cvImport, Enum.Action.IMPORT);
setButtonClickListener(binding.cvExport, Enum.Action.EXPORT);
}
@SuppressLint("ClickableViewAccessibility")
private void setButtonClickListener(CardView button, String target)
{
button.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
TMSound.playButtonSound(this);
setButtonStyle_Pressed(button);
} else {
if (event.getAction() == MotionEvent.ACTION_UP) {
setButtonStyle_Released(button);
switch (target) {
case (Enum.Action.IMPORT):
selectFile();
break;
case (Enum.Action.EXPORT):
onTouchExport();
break;
}
}
}
return true;
});
}
private void onTouchExport()
{
//check if there is data to export
TMSession us = new TMSession();
User user = us.getUserSession();
List<RecordDTO> records = UserService.getRecords(user.getId(), "");
if (records.size()>0){
exportData();
}else{
String strError = TMLocale.getStringResourceByName("activityimportexport_nodatatoexport");
TMToast toast = new TMToast(this);
toast.show(strError, Toast.LENGTH_SHORT);
}
}
private void configureButton(TextView button)
{
int dpButtonHeight = TMActivity.getButtonsHeight(3); // TMScreen.dp2px(height);
button.setHeight(dpButtonHeight);
button.setTextSize(TMAppearance.getPrefsByScreenDensity()[3]);
}
/**
* Imports records data from file into records table
*/
private void selectFile()
{
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
intent.addCategory(Intent.CATEGORY_OPENABLE);
activityResultLauncher.launch(intent);
}
private void exportData()
{
String recordsData = TMExport.getRecordsData();
try {
filename = getFilename();
File filelocation = TMFile.writeFile(this, filename, recordsData);
sendExportFileTo(filelocation);
} catch (IOException e) {
TMToast toast = new TMToast(this);
toast.show(e.getMessage(), Toast.LENGTH_SHORT);
ExceptionHandler.logException(e);
}
}
private String getFilename()
{
SimpleDateFormat formatter = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.ENGLISH);
Date now = new Date();
return "testme_exportfile_" + formatter.format(now) + ".txt";
}
private void sendExportFileTo(File attachment)
{
Uri uri = FileProvider.getUriForFile(this, this.getApplicationContext().getPackageName() + ".provider", attachment);
Intent i = new Intent(Intent.ACTION_SEND);
i.setType("text/plain");
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
i.putExtra(Intent.EXTRA_EMAIL, new String[] { "" });
String subject = TMLocale.getStringResourceByName("importexport_exporttomail_subject") + " " + filename;
String body = TMLocale.getStringResourceByName("importexport_exporttomail_body");
i.putExtra(Intent.EXTRA_SUBJECT, subject);
i.putExtra(Intent.EXTRA_TEXT, body);
i.putExtra(Intent.EXTRA_STREAM, uri);
try {
this.startActivity(Intent.createChooser(i, "Send mail..."));
} catch (android.content.ActivityNotFoundException e) {
Toast.makeText(this,
TMLocale.getStringResourceByName("importexport_exporttomail_noemailclients"), Toast.LENGTH_SHORT)
.show();
ExceptionHandler.logException(e);
}
}
}
to Kotlin I ended with the. next class:
class ImportExportActivity : BaseActivity() {
private var filename: String? = null
private var activityResultLauncher: ActivityResultLauncher<Intent>? = null
private var binding: ActivityImportexportBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setImportDataActivityResultHandler()
}
private fun setImportDataActivityResultHandler() {
activityResultLauncher = registerForActivityResult<Intent, ActivityResult>(
StartActivityForResult(),
ActivityResultCallback { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
val data = result.data
if (data != null) {
oData = data
val hasPermission =
TMPermission.verifyStorageReadPermissions(AppSettings.getContext() as Activity)
appSettings.requestPermissionSource = RequestPermissionSource.IMPORTDATA
if (hasPermission) {
val strResult = TMImport.importData(data)
val toast = TMToast(AppSettings.getContext())
toast.show(strResult, Toast.LENGTH_LONG)
}
}
}
})
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (appSettings.requestPermissionSource
== RequestPermissionSource.IMPORTDATA
) {
if (TMPermission.hasReadPermission()) {
val strResult = TMImport.importData(oData)
val toast = TMToast(this)
toast.show(strResult, Toast.LENGTH_LONG)
}
}
}
override fun setActivityLayout() {
super.setActivityLayout()
setButtonsAppearance()
}
override fun setContentView() {
binding = DataBindingUtil.setContentView(this, R.layout.activity_importexport)
}
override fun setActivityBackground() {
binding!!.llImportExportMain.setBackgroundColor(
Color.parseColor(
appSettings.mainLayoutBackgroundColor
)
)
}
override fun createActionBar() {
val activityTitle =
TMLocale.getStringResourceByName("activityrecords_textview_importexport").uppercase(
Locale.getDefault()
)
createActionBar(this, activityTitle)
}
override fun setFooterMenu() {
val footerBinding = binding!!.footermenu
TMFooter.footerBinding = WeakReference(footerBinding)
TMFooter.activity = WeakReference(this)
TMFooter.dialog = WeakReference(dialog)
TMFooter.setFooterMenu()
}
private fun setButtonsAppearance() {
configureButton(binding!!.btnImport)
configureButton(binding!!.btnExport)
setButtonClickListener(binding!!.cvImport, Enum.Action.IMPORT)
setButtonClickListener(binding!!.cvExport, Enum.Action.EXPORT)
}
@SuppressLint("ClickableViewAccessibility")
private fun setButtonClickListener(button: CardView, target: String) {
button.setOnTouchListener { v: View?, event: MotionEvent ->
if (event.action == MotionEvent.ACTION_DOWN) {
TMSound.playButtonSound(this)
setButtonStyle_Pressed(button)
} else {
if (event.action == MotionEvent.ACTION_UP) {
setButtonStyle_Released(button)
when (target) {
Enum.Action.IMPORT -> selectFile()
Enum.Action.EXPORT -> onTouchExport()
}
}
}
true
}
}
private fun onTouchExport() {
//check if there is data to export
val us = TMSession()
val user = us.userSession
val records = UserService.getRecords(user.id, "")
if (records.size > 0) {
exportData()
} else {
val strError = TMLocale.getStringResourceByName("activityimportexport_nodatatoexport")
val toast = TMToast(this)
toast.show(strError, Toast.LENGTH_SHORT)
}
}
private fun configureButton(button: TextView) {
val dpButtonHeight = getButtonsHeight(3) // TMScreen.dp2px(height);
button.height = dpButtonHeight
button.textSize = prefsByScreenDensity[3].toFloat()
}
/**
* Imports records data from file into records table
*/
private fun selectFile() {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "*/*"
intent.addCategory(Intent.CATEGORY_OPENABLE)
activityResultLauncher!!.launch(intent)
}
private fun exportData() {
val recordsData = TMExport.getRecordsData()
try {
filename = getFilename()
val filelocation = TMFile.writeFile(this, filename, recordsData)
sendExportFileTo(filelocation)
} catch (e: IOException) {
val toast = TMToast(this)
toast.show(e.message, Toast.LENGTH_SHORT)
ExceptionHandler.logException(e)
}
}
private fun getFilename(): String {
val formatter = SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.ENGLISH)
val now = Date()
return "testme_exportfile_" + formatter.format(now) + ".txt"
}
private fun sendExportFileTo(attachment: File) {
val uri = FileProvider.getUriForFile(
this,
this.applicationContext.packageName + ".provider",
attachment
)
val i = Intent(Intent.ACTION_SEND)
i.type = "text/plain"
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
i.putExtra(Intent.EXTRA_EMAIL, arrayOf(""))
val subject =
TMLocale.getStringResourceByName("importexport_exporttomail_subject") + " " + filename
val body = TMLocale.getStringResourceByName("importexport_exporttomail_body")
i.putExtra(Intent.EXTRA_SUBJECT, subject)
i.putExtra(Intent.EXTRA_TEXT, body)
i.putExtra(Intent.EXTRA_STREAM, uri)
try {
this.startActivity(Intent.createChooser(i, "Send mail..."))
} catch (e: ActivityNotFoundException) {
Toast.makeText(
this,
TMLocale.getStringResourceByName("importexport_exporttomail_noemailclients"),
Toast.LENGTH_SHORT
)
.show()
ExceptionHandler.logException(e)
}
}
companion object {
private var oData: Intent? = null
}
}
and "registerForActivityResult" stopped being recognised by compiler.
As I've read here: Android ActivityResult API unresolved reference error registerForActivityResult
the solution passes by importing these next two packages in build.gradle:
api 'androidx.activity:activity-ktx:1.6.0-alpha05'
api 'androidx.fragment:fragment-ktx:1.5.0-rc01'
and then making your activity extend ComponentActivity.
Well, I tried this solutions and it works, "registerforactivityresult" is now recognised, but I cannot inherit from ComponentActivity because all my activities extend BaseActivity, and BaseActivity extends AppCompatActivity, so I cannot extend ComponentActivity in BaseActivity either.
Said that, I'm stuck and don't know how to use registerforactivityresult in Kotlin given my scenario.
Edit 1:
This is my build.gradle support section:
api 'androidx.legacy:legacy-support-v4:1.0.0'
api 'androidx.appcompat:appcompat:1.4.2'
api 'androidx.cardview:cardview:1.0.0'
api 'androidx.recyclerview:recyclerview:1.2.1'
api 'androidx.preference:preference-ktx:1.2.0'
Edit 2:
Not sure if it will be the final solution, but replacing AppCompatActivity by ComponentActivity in the BaseActivity class solved de compilation problem.
Edit 3:
Changing AppCompatActivity by ComponentActivity ended being more an illusion than a solution. If I extend ComponentActivity the registerforactivityresult start working, but FragmentActivities stop compiling because to use SectionsPagerAdapter my class needs to extend FragmentActivity. This is like a vicious circle.
Edit 4:
My complete build.gradle:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 32
def code
Properties versionProps = new Properties()
def versionPropsFile = file('version.properties')
if (versionPropsFile.exists())
versionProps.load(new FileInputStream(versionPropsFile))
code = (versionProps['VERSION_CODE'] ?: "0").toInteger()+ 1
packagingOptions {
resources {
pickFirsts += ['META-INF/LICENSE.txt']
excludes += ['META-INF/NOTICE.md', 'META-INF/LICENSE.md', 'META-INF/INDEX.LIST', 'META-INF/DEPENDENCIES', 'META-INF/io.netty.versions.properties']
}
}
versionProps['VERSION_CODE'] = code.toString()
versionProps.store(versionPropsFile.newWriter(), null)
defaultConfig {
applicationId 'com.xxx.xxx'
minSdkVersion 26
targetSdkVersion 32
multiDexEnabled true
versionCode code
versionName "3.0." + code
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
bundle {
density {
// Different APKs are generated for devices with different screen densities; true by default.
enableSplit true
}
abi {
// Different APKs are generated for devices with different CPU architectures; true by default.
enableSplit true
}
language {
// This is disabled so that the App Bundle does NOT split the APK for each language.
// We're gonna use the same APK for all languages.
enableSplit false
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
applicationVariants.all { variant ->
variant.outputs.all { output ->
project.ext { appName = 'xxx' }
def newName = 'xxx.apk'
outputFileName = new File("./build/", newName)
}
}
}
debug {
}
}
productFlavors {
}
dataBinding{
enabled = true
}
lint {
abortOnError false
checkReleaseBuilds false
}
namespace 'com.xxx.xxx'
}
dependencies {
api files('libs/achartengine-1.2.0.jar')
api 'com.facebook.android:facebook-android-sdk:14.0.0'
//mail API 16
implementation 'com.sun.mail:android-mail:1.6.7'
implementation 'com.sun.mail:android-activation:1.6.7'
//apache commons text
implementation group: 'org.apache.commons', name: 'commons-text', version: '1.9'
//Youtube player
api 'com.thefinestartist:ytpa:1.2.1'
//Font Selector List Preference
//api 'com.vanniktech:vntfontlistpreference:1.0.0'
//Rate my app
api 'com.github.hotchemi:android-rate:1.0.1'
//Support
api 'androidx.legacy:legacy-support-v4:1.0.0'
api 'androidx.appcompat:appcompat:1.4.2'
api 'androidx.cardview:cardview:1.0.0'
api 'androidx.recyclerview:recyclerview:1.2.1'
api 'androidx.preference:preference-ktx:1.2.0'
//Annotation
api 'androidx.annotation:annotation:1.4.0'
//AlertDialog
api 'com.github.d-max:spots-dialog:1.1@aar'
//glide animated gifs
api 'com.github.bumptech.glide:glide:4.13.2'
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.2'
//circular score
api 'com.wssholmes.stark:circular-score:0.1.1'
api 'com.google.android.material:material:1.6.1'
api 'com.github.mejdi14:Flat-Dialog-Android:1.0.5'
//picasso
api 'com.squareup.picasso:picasso:2.71828'
//Gson
implementation 'com.google.code.gson:gson:2.9.0'
//Stream
api 'com.annimon:stream:1.2.2'
api 'androidx.activity:activity-ktx:1.6.0-alpha05'
api 'androidx.activity:activity-ktx:1.4.0'
api 'androidx.fragment:fragment-ktx:1.5.0-rc01'
/*implementation 'jp.wasabeef:picasso-transformations:2.4.0'*/
//zoom
//implementation 'com.alexvasilkov:gesture-views:2.8.2'
//implementation 'com.otaliastudios:zoomlayout:1.8.0'
//Multidex
implementation "androidx.multidex:multidex:2.0.1"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation project(':Global')
implementation project(':DTO')
implementation project(':Common')
implementation project(':Background')
implementation project(':Services')
}
The weird thing is that even the lint error the app builds just fine, but as I'm in the process of migrating wasn't able to test registerForActivityResult yet, but I guess it most likely won't work.
Edit 5:
In this next Google page: https://developer.android.com/jetpack/androidx/releases/activity
says:
"The registerForActivityResult() API has been renamed to rememberLauncherForActivityResult()..."
but the same happens with rememberLauncherForActivityResult, it's not found. I'm completely stuck.
Edit 6:
As far as I've been reading, maybe it's only a "visual" problem, let's say an Android Studio IDE issue, because the app builds fine (I haven't tested the registerForActivityResult yet, though), but anyway I'm fed up with Google.
As time goes by, packages are marked as obsolete sooner everytime, and the amazing thing is that the "replacement" not always work.
For the moment I'll leave my "registerForActivityResult" issue as it is (as I have no choice), but honestly, and in my opinion, Google discourage us, developers, to keep our apps up to date.