Недавно я обнаружил, что onActivityResult
устарел. Что нам делать, чтобы справиться с этим?
Для этого есть какая-нибудь альтернатива?
Недавно я обнаружил, что onActivityResult
устарел. Что нам делать, чтобы справиться с этим?
Для этого есть какая-нибудь альтернатива?
Базовое обучение доступно на developer.android.com.
Вот пример того, как преобразовать существующий код в новый:
Старый способ:
public void openSomeActivityForResult() {
Intent intent = new Intent(this, SomeActivity.class);
startActivityForResult(intent, 123);
}
@Override
protected void onActivityResult (int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK && requestCode == 123) {
doSomeOperations();
}
}
Новый способ (Java):
// You can do the assignment inside onAttach or onCreate, i.e, before the activity is displayed
ActivityResultLauncher<Intent> someActivityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
// There are no request codes
Intent data = result.getData();
doSomeOperations();
}
}
});
public void openSomeActivityForResult() {
Intent intent = new Intent(this, SomeActivity.class);
someActivityResultLauncher.launch(intent);
}
Новый способ (Котлин):
var resultLauncher = registerForActivityResult(StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
// There are no request codes
val data: Intent? = result.data
doSomeOperations()
}
}
fun openSomeActivityForResult() {
val intent = Intent(this, SomeActivity::class.java)
resultLauncher.launch(intent)
}
ИЗМЕНИТЬ. Лучше сделать его более универсальным, чтобы мы могли его повторно использовать. Приведенный ниже фрагмент используется в одном из моих проектов, но учтите, что он недостаточно протестирован и может не охватывать все случаи.
BetterActivityResult.java
import android.content.Intent;
import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCaller;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContract;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class BetterActivityResult<Input, Result> {
/**
* Register activity result using a {@link ActivityResultContract} and an in-place activity result callback like
* the default approach. You can still customise callback using {@link #launch(Object, OnActivityResult)}.
*/
@NonNull
public static <Input, Result> BetterActivityResult<Input, Result> registerForActivityResult(
@NonNull ActivityResultCaller caller,
@NonNull ActivityResultContract<Input, Result> contract,
@Nullable OnActivityResult<Result> onActivityResult) {
return new BetterActivityResult<>(caller, contract, onActivityResult);
}
/**
* Same as {@link #registerForActivityResult(ActivityResultCaller, ActivityResultContract, OnActivityResult)} except
* the last argument is set to {@code null}.
*/
@NonNull
public static <Input, Result> BetterActivityResult<Input, Result> registerForActivityResult(
@NonNull ActivityResultCaller caller,
@NonNull ActivityResultContract<Input, Result> contract) {
return registerForActivityResult(caller, contract, null);
}
/**
* Specialised method for launching new activities.
*/
@NonNull
public static BetterActivityResult<Intent, ActivityResult> registerActivityForResult(
@NonNull ActivityResultCaller caller) {
return registerForActivityResult(caller, new ActivityResultContracts.StartActivityForResult());
}
/**
* Callback interface
*/
public interface OnActivityResult<O> {
/**
* Called after receiving a result from the target activity
*/
void onActivityResult(O result);
}
private final ActivityResultLauncher<Input> launcher;
@Nullable
private OnActivityResult<Result> onActivityResult;
private BetterActivityResult(@NonNull ActivityResultCaller caller,
@NonNull ActivityResultContract<Input, Result> contract,
@Nullable OnActivityResult<Result> onActivityResult) {
this.onActivityResult = onActivityResult;
this.launcher = caller.registerForActivityResult(contract, this::callOnActivityResult);
}
public void setOnActivityResult(@Nullable OnActivityResult<Result> onActivityResult) {
this.onActivityResult = onActivityResult;
}
/**
* Launch activity, same as {@link ActivityResultLauncher#launch(Object)} except that it allows a callback
* executed after receiving a result from the target activity.
*/
public void launch(Input input, @Nullable OnActivityResult<Result> onActivityResult) {
if (onActivityResult != null) {
this.onActivityResult = onActivityResult;
}
launcher.launch(input);
}
/**
* Same as {@link #launch(Object, OnActivityResult)} with last parameter set to {@code null}.
*/
public void launch(Input input) {
launch(input, this.onActivityResult);
}
private void callOnActivityResult(Result result) {
if (onActivityResult != null) onActivityResult.onActivityResult(result);
}
}
При описанном выше подходе вам все равно необходимо зарегистрировать его до или во время запуска активности или прикрепления фрагмента. После определения его можно повторно использовать в действии или фрагменте. Например, если вам нужно начать новые действия в большей части действия, вы можете определить BaseActivity
и зарегистрировать новый BetterActivityResult
следующим образом:
BaseActivity.java
public class BaseActivity extends AppCompatActivity {
protected final BetterActivityResult<Intent, ActivityResult> activityLauncher = BetterActivityResult.registerActivityForResult(this);
}
После этого вы можете просто запустить действие из любых дочерних действий, например:
public void openSomeActivityForResult() {
Intent intent = new Intent(this, SomeActivity.class);
activityLauncher.launch(intent, result -> {
if (result.getResultCode() == Activity.RESULT_OK) {
// There are no request codes
Intent data = result.getData();
doSomeOperations();
}
})
}
Поскольку вы можете установить функцию обратного вызова вместе с Intent
, вы можете повторно использовать ее для любых действий.
Точно так же вы также можете использовать другие контракты действий, используя два других конструктора.
ActivityResultCallback
должен быть частью ActivityResultLauncher#launch()
, чтобы мы могли повторно использовать его для других целей, например, если бы мне нужно было проверить права доступа к хранилищу для нескольких действий, я мог бы использовать один ActivityResultContracts.RequestPermission()
для всех из них. Вы можете реализовать ActivityResultCallback
в попытке решить эту проблему, но это будет хуже, чем старый способ, поскольку вам нужно что-то похожее на коды запросов.
- person Muntashir Akon; 11.09.2020
result codes
. Нам пришлось добавить разные константы для каждого действия. Иногда мы могли смешивать, и два кода результата становились равными. Это могло привести к различным ошибкам.
- person CoolMind; 25.01.2021
onActivityResult
. Одна активность содержит 10 фрагментов, некоторые из них могут запускать другие активности и возвращать результат. Раньше мы просто возвращали setResult()
во фрагменте и обрабатывали в onActivityResult
. Теперь нам нужно создать множество обратных вызовов.
- person CoolMind; 25.01.2021
ViewPager
в пользу ViewPager2
, что создало больше проблем, чем решило что-либо.
- person Muntashir Akon; 28.01.2021
onActivityResult
является отдельным обратным вызовом, а не лямбда, который вы установили в launch
time, заключается в том, что он должен существовать после изменения конфигурации или процесса смерти / восстановления (оба из которых могут произойти, пока другое действие открыто - просто поверните ваше устройство для простого случая). Ваш подход на основе лямбда никогда не сможет правильно обработать эти случаи.
- person ianhanniballake; 01.06.2021
BetterActivityResult
также необходимо инициализировать во время или перед созданием фрагмента / действия, может быть способ обработать их внутри класса.
- person Muntashir Akon; 01.06.2021
startActivityForResult2
. Если вы думаете, что работа с результирующими кодами была утомительной, просто подождите, пока вы получите массу этого беспорядка.
- person rmirabelle; 03.06.2021
С этого момента startActivityForResult()
устарел, поэтому используйте вместо него новый метод.
Пример Котлина
fun openActivityForResult() {
startForResult.launch(Intent(this, AnotherActivity::class.java))
}
val startForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
val intent = result.data
// Handle the Intent
//do stuff here
}
}
В КОТЛИНЕ я изменил свой код
startActivityForResult(intent, Constants.MY_CODE_REQUEST)
а также
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
Constants.MY_CODE_REQUEST -> {
...
}
to
registerForActivityResult(StartActivityForResult()) { result ->
onActivityResult(Constants.MY_CODE_REQUEST, result)
}.launch(intent)
а также
private fun onActivityResult(requestCode: Int, result: ActivityResult) {
if(result.resultCode == Activity.RESULT_OK) {
val intent = result.data
when (requestCode) {
Constants.MY_CODE_REQUEST -> {
...
Надеюсь, у вас это сработает. : D
onActivityResult
из вашего третьего фрагмента кода на registerForActivityResult
устарела.
- person Filipe Brito; 27.02.2021
registerForActivityResult
Преимущество:
В Котлине:
var launchSomeActivity = registerForActivityResult(StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
val data: Intent? = result.data
// your operation...
}
}
fun openYourActivity() {
val intent = Intent(this, SomeActivity::class.java)
launchSomeActivity.launch(intent)
}
В Java:
// Create lanucher variable inside onAttach or onCreate or global
ActivityResultLauncher<Intent> launchSomeActivity = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
Intent data = result.getData();
// your operation....
}
}
});
public void openYourActivity() {
Intent intent = new Intent(this, SomeActivity.class);
launchSomeActivity.launch(intent);
}
android.content.Intent intent, int requestCode
в качестве параметров, например, в устаревшем методе?
- person AtomX; 10.04.2021
В Java это можно записать примерно так:
ActivityResultLauncher<Intent> startActivityForResult = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == AppCompatActivity.RESULT_OK) {
Intent data = result.getData();
// ...
}
}
);
Intent intent = new Intent( ... );
startActivityForResult.launch(intent);
Моей целью было повторно использовать текущую реализацию метода startActivityForResult
с минимальными изменениями кода. Для этого я создал класс-оболочку и интерфейс с методом onActivityResultFromLauncher.
interface ActivityResultLauncherWrapper {
fun launchIntentForResult(activity: FragmentActivity, intent: Intent, requestCode: Int, callBack: OnActivityResultListener)
fun unregister()
interface OnActivityResultListener {
fun onActivityResultFromLauncher(requestCode: Int, resultCode: Int, data: Intent?)
}
}
class ActivityResultLauncherWrapperImpl : ActivityResultLauncherWrapper {
private var weakLauncher: WeakReference<ActivityResultLauncher<Intent>>? = null
override fun launchIntentForResult(
activity: FragmentActivity,
intent: Intent,
requestCode: Int,
callBack: ActivityResultLauncherWrapper.OnActivityResultListener
) {
weakLauncher = WeakReference(
activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
callBack.onActivityResultFromLauncher(requestCode, result.resultCode, result.data)
}
)
weakLauncher?.get()?.launch(intent)
}
override fun unregister() {
weakLauncher?.get()?.unregister()
}
}
Я использую Dagger в своем проекте и внедрил обертку там, где она нужна
@Inject
lateinit var activityResultLauncher: ActivityResultLauncherWrapper
Но обертка также может быть создана напрямую:
val activityResultLauncher = ActivityResultLauncherWrapper()
тогда вам нужно изменить метод startActivityForResult
на launchIntentForResult
. Вот пример вызова из фрагмента:
activityResultLauncher.launchIntentForResult(
requireActivity(),
intent,
REQUEST_CODE_CONSTANT,
object: ActivityResultLauncherWrapper.OnActivityResultListener {
override fun onActivityResultFromLauncher(requestCode: Int, resultCode: Int, data: Intent?) {
/*do something*/
}
}
)
Вы получите результат в виде анонимного объекта. Вы можете использовать OnActivityResultListener
во фрагменте или FragmentActivity, если вы реализуете интерфейс и реорганизуете текущую реализацию следующим образом:
class MyFragment : Fragment(), OnActivityResultListener {
...
override fun onActivityResultFromLauncher(requestCode: Int, resultCode: Int, data: Intent?) {/*do somthing*/}
...
}
Как мы знаем, класс Kotlin ActivityResultLauncherWrapper также можно использовать в java-коде. В моем проекте тоже есть классы java. Вот пример реализации интерфейса обратного вызова во фрагменте:
public class MyFragment extends Fragment implements OnActivityResultListener {
...
@Inject
ActivityResultLauncherWrapper activityResultLauncher;
//ActivityResultLauncherWrapper activityResultLauncher = new ActivityResultLauncherWrapper()
...
public void launnchActivity(@NotNull Intent intent) {
activityResultLauncher.launchIntentForResult(requireActivity(), intent, REQUEST_CODE_CONSTANT, this);
}
...
@Override
public void onActivityResultFromLauncher(int requestCode, int resultCode, Intent data) {/*do somthing*/}
...
}
Надеюсь, это поможет найти решение для вашего случая.
Ссылка: Kotlin - Выберите изображение из галереи
Самое простое оповещение, которое я нашел на данный момент
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.id.activity_main)
var ivPhoto = findViewById<ImageView>(R.id.ivPhoto)
var btnChoosePhoto = findViewById<Button>(R.id.btnChoosePhoto)
val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? ->
ivPhoto.setImageURI(uri) // Handle the returned Uri
}
btnChoose.setOnClickListener {
getContent.launch("image/*")
}
}
Вот мое решение:
В нашем проекте у нас было более 20 экземпляров startActivityForResult (и onActivityResult).
Мы хотели как можно меньше менять код (и продолжать использовать коды запросов), но при этом представили элегантное решение для будущего использования.
Поскольку многие из нас, разработчиков, используют концепцию BaseActivity, почему бы не воспользоваться ею?
Вот BaseActivity:
abstract class BaseActivity : AppCompatActivity()
{
private var requestCode: Int = -1
private var resultHandler: ActivityResultLauncher<Intent>? = null
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
registerForActivityResult()
}
private fun registerForActivityResult()
{
if (shouldRegisterForActivityResult())
{
resultHandler = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
onActivityResult(result.data, requestCode, result.resultCode)
this.requestCode = -1
}
}
}
fun startActivityForResult(requestCode: Int, intent: Intent)
{
this.requestCode = requestCode
resultHandler?.launch(intent)
}
protected open fun onActivityResult(data: Intent?, requestCode: Int, resultCode: Int)
{
// For sub activities
}
protected open fun shouldRegisterForActivityResult(): Boolean
{
// Sub activities that need the onActivityResult "mechanism", should override this and return true
return false
}
}
Вот SubActivity:
class SubActivity : BaseActivity()
{
companion object
{
private const val SOME_REQUEST_CODE = 300
}
private fun testActivityResult()
{
val intent = Intent(this, OtherActivity::class.java)
startActivityForResult(SOME_REQUEST_CODE, intent)
}
override fun shouldRegisterForActivityResult(): Boolean
{
return true
}
override fun onActivityResult(data: Intent?, requestCode: Int, resultCode: Int)
{
if (requestCode == SOME_REQUEST_CODE)
{
// Yes!
}
}
}
Надеюсь, это кому-то поможет
Кажется, что onActivityResult
устарел в суперклассе, но вы не упомянули имя суперкласса и compileSdkVersion
здесь, в своем вопросе.
В Java и Kotlin каждый класс или метод можно было бы пометить как устаревший, просто добавив к нему @Deprecated
, поэтому проверьте свой суперкласс, вы можете расширить неправильный класс.
Когда класс устарел, все его методы также устарели.
Чтобы увидеть быстрое решение, щелкните устаревший метод и нажмите Ctrl+Q
в студии Android, чтобы просмотреть документацию по методу, для него должно быть решение.
В моем проекте, использующем androidx
и API 29 как compileSdkVersion
, этот метод НЕ является устаревшим в действиях и фрагментах.
Вы можете использовать функции расширения для Колтина. Например:
//random utils file
fun Fragment.buildGetContentRequest(function: (Uri) -> Unit): ActivityResultLauncher<String> {
return this.registerForActivityResult(ActivityResultContracts.GetContent()) {
function(it)
}
}
fun Fragment.buildTakePhotoRequest(function: (Boolean) -> Unit): ActivityResultLauncher<Uri> {
return this.registerForActivityResult(ActivityResultContracts.TakePicture()) {
function(it)
}
}
fun Fragment.buildSelectMultipleContentRequest(function: (MutableList<Uri>?) -> Unit): ActivityResultLauncher<String> {
return this.registerForActivityResult(ActivityResultContracts.GetMultipleContents()) {
function(it)
}
}
А потом в вашем фрагменте что-то вроде этого
//your actual fragment logic
class YourFragment : Fragment() {
//we can assign our request in init process
private val mRequestSelectFiles = buildSelectMultipleContentRequest {
onFilesSelected(it)
}
fun onSelectFiles() {
val mime = "*/*"
mRequestSelectFiles.launch(mime)
}
fun onFilesSelected(list: MutableList<Uri>?) {
//your logic
}
}
Котлинская версия решения @Muntashir Akon
class BetterActivityResult<Input, Result> private constructor(
caller : ActivityResultCaller,
contract : ActivityResultContract<Input, Result>,
var onActivityResult : ((Result) -> Unit)?,
) {
private val launcher : ActivityResultLauncher<Input> =
caller.registerForActivityResult(contract) { onActivityResult?.invoke(it) }
/**
* Launch activity, same as [ActivityResultLauncher.launch] except that it
* allows a callback
* executed after receiving a result from the target activity.
*/
/**
* Same as [.launch] with last parameter set to `null`.
*/
@JvmOverloads
fun launch(
input : Input,
onActivityResult : ((Result) -> Unit)? = this.onActivityResult,
) {
this.onActivityResult = onActivityResult
launcher.launch(input)
}
companion object {
/**
* Register activity result using a [ActivityResultContract] and an in-place
* activity result callback like
* the default approach. You can still customise callback using [.launch].
*/
fun <Input, Result> registerForActivityResult(
caller : ActivityResultCaller,
contract : ActivityResultContract<Input, Result>,
onActivityResult : ((Result) -> Unit)?,
) : BetterActivityResult<Input, Result> {
return BetterActivityResult(caller, contract, onActivityResult)
}
/**
* Same as [.registerForActivityResult] except
* the last argument is set to `null`.
*/
fun <Input, Result> registerForActivityResult(
caller : ActivityResultCaller,
contract : ActivityResultContract<Input, Result>,
) : BetterActivityResult<Input, Result> {
return registerForActivityResult(caller, contract, null)
}
/**
* Specialised method for launching new activities.
*/
fun registerActivityForResult(
caller : ActivityResultCaller,
) : BetterActivityResult<Intent, ActivityResult> {
return registerForActivityResult(caller, StartActivityForResult())
}
}
}
startActivityForResult и onActivityResult устарели в Android 10 API 30, теперь у нас есть новый способ получить результат с помощью registerForActivityResult
resultContract =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
// There are no request codes
val country = result.data?.getParcelableExtra<Country>("Country")
showLiveDemoDialogue(country)
}
}
и начать деятельность
val intent = Intent(this, CountriesListActivity::class.java)
resultContract.launch(intent)
но вы должны зарегистрироваться, прежде чем вызывать запуск и запускать где хотите. в противном случае вы получите это исключение
attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.
Другой способ сделать это - сделать это за 3 шага. (Учитывая, что у вас есть startActivityForResult (0 и onActivityResult ())
var resultLauncher:ActivityResultLauncher<Intent>
resultLauncher=registerForActivityResult(ActivityResultContracts.StartActivityForResult()){result ->
// copy paste the code from the onActivityResult replacing resultcode to result.resultCode
if(result.resultcode==Activity.Result_OK){
val data=result.data // this data variable is of type intent and you can use it
}else{
//code if you do not get the data
}
}
startActivityForResult()
и замените ее строкой resultLauncher.launch(intent)
registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { activityResult ->
if (activityResult.resultCode == Activity.RESULT_OK) {
//...
}
}
Требуется разрешение от Activity?
registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) {
//it: Map<String, Boolean>
}
Используйте те же методы, но убедитесь, что вы поместили эти реализации в initialization, onAttach(), or onCreate()
startActivityForResult
. Этот новый способ чрезмерно усложняет код и снижает удобочитаемость. - person spartygw   schedule 05.03.2021startActivityForResult
от жизненного цикла представления. Мне просто хотелось, чтобы это было более элегантно. - person Morgan Koh   schedule 06.07.2021