Обращение назад в компоненте Android Navigation

Я хотел бы знать, как правильно обрабатывать действия кнопки возврата системы с помощью Navigation Controller. В моем приложении у меня есть два фрагмента (например, фрагмент1 и фрагмент2), и у меня есть действие в фрагменте 1 с назначением для фрагмента2. Все работает хорошо, кроме одного - когда пользователь нажимает кнопку возврата системы во фрагменте 2, я хочу показать диалог (например, используя DialogFragment) для подтверждения выхода. Каков наилучший способ реализовать это поведение? Если я использую app:defaultNavHost="true" в моем фрагменте хоста, он автоматически возвращается, игнорируя мои правила. И, кроме того, для чего этот компонент?

enter image description here

Должен ли я использовать "поп к" может быть?

Ответ 1

Последнее обновление - 25 апреля 2019 г.

Новый выпуск androidx.activity ver. 1.0.0-alpha07 вносит некоторые изменения

Дополнительные объяснения в официальном руководстве Android: Обеспечить пользовательскую обратную навигацию

Пример:

public class MyFragment extends Fragment {

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // This callback will only be called when MyFragment is at least Started.
        OnBackPressedCallback callback = new OnBackPressedCallback(true /* enabled by default */) {
            @Override
            public void handleOnBackPressed() {
                // Handle the back button event
            }
        });
        requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);

        // The callback can be enabled or disabled here or in handleOnBackPressed()
    }
    ...
}

Старые обновления

UPD: 3 апреля 2019 года

Теперь это упрощено. Подробнее здесь

Пример:

requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), this);

@Override
public boolean handleOnBackPressed() {
    //Do your job here
    //use next line if you just need navigate up
    //NavHostFragment.findNavController(this).navigateUp(); 
    //Log.e(getClass().getSimpleName(), "handleOnBackPressed");
    return true;
    }

Устаревший (с версии 1.0.0-alpha06 3 апреля 2019 г.):

Поскольку это, его можно реализовать, просто используя реализацию JetPack OnBackPressedCallback в вашем фрагменте. и добавьте его в активность: getActivity().addOnBackPressedCallback(getViewLifecycleOwner(),this);

Ваш фрагмент должен выглядеть следующим образом:

public MyFragment extends Fragment implements OnBackPressedCallback {

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        getActivity().addOnBackPressedCallback(getViewLifecycleOwner(),this);
}

    @Override
    public boolean handleOnBackPressed() {
        //Do your job here
        //use next line if you just need navigate up
        //NavHostFragment.findNavController(this).navigateUp(); 
        //Log.e(getClass().getSimpleName(), "handleOnBackPressed");
        return true;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        getActivity().removeOnBackPressedCallback(this);
    }
}

UPD: Ваша активность должна быть расширена AppCompatActivity или FragmentActivity и в файле Gradle:

 implementation 'androidx.appcompat:appcompat:{lastVersion}'

Ответ 2

Итак, я создал интерфейс

public interface OnBackPressedListener {
    void onBackPressed();
}

И реализовал его всеми фрагментами, которые нужно обработать кнопкой возврата. В основном onBackPressed() я переопределил onBackPressed():

@Override
public void onBackPressed() {
    final Fragment currentFragment = mNavHostFragment.getChildFragmentManager().getFragments().get(0);
    final NavController controller = Navigation.findNavController(this, R.id.nav_host_fragment);
    if (currentFragment instanceof OnBackPressedListener)
        ((OnBackPressedListener) currentFragment).onBackPressed();
    else if (!controller.popBackStack())
        finish();

}

Итак, если верхний фрагмент моего навигационного узла реализует интерфейс OnBackPressedListener, я вызываю его onBackPressed(), в другом месте я просто отбрасываю стек и закрываю приложение, если задний стек пуст.

Ответ 3

рекомендуемый подход заключается в добавлении OnBackPressedCallback к действию OnBackPressedDispatcher.

requireActivity().onBackPressedDispatcher.addCallback { 
    // handle back event
}

Ответ 4

Вот решение, которое должно делать то, что вы хотите, но я думаю, что это плохое решение, потому что оно идет против идеи Android Navigation (позволяя андроиду управлять навигацией).

Переопределить "onBackPressed" внутри вашей деятельности

override fun onBackPressed() {
    when(NavHostFragment.findNavController(nav_host_fragment).currentDestination.id) {
        R.id.fragment2-> {
            val dialog=AlertDialog.Builder(this).setMessage("Hello").setPositiveButton("Ok", DialogInterface.OnClickListener { dialogInterface, i ->
                finish()
            }).show()
        }
        else -> {
            super.onBackPressed()
        }
    }
} 

Ответ 5

Немного опоздал на вечеринку, но с последней версией Navigation Component 1.0.0-alpha09, теперь у нас есть AppBarConfiguration.OnNavigateUpListener.

Обратитесь к этим ссылкам для получения дополнительной информации: https://developer.android.com/reference/androidx/navigation/ui/AppBarConfiguration.OnNavigateUpListener https://developer.android.com/jetpack/docs/release-notes

Ответ 6

В 2.1.0-alpha06

Если вы хотите обрабатывать обратную печать только в текущем фрагменте

requireActivity().onBackPressedDispatcher.addCallback([email protected]) {
    // handle back event
}

Для всей деятельности

requireActivity().onBackPressedDispatcher.addCallback() {
    // handle back event
}

Ответ 7

Для тех, кто ищет реализацию Kotlin, см. ниже.

Обратите внимание, что OnBackPressedCallback, по-видимому, работает только для обеспечения настраиваемого поведения назад для встроенной программной/аппаратной кнопки "назад", а не для кнопки "стрелка назад"/home в качестве кнопки "вверх" на панели действий/панели инструментов. Чтобы также переопределить поведение кнопки возврата панели действий/панели инструментов, я предлагаю решение, которое работает для меня. Если это ошибка или вам известно о лучшем решении для этого случая, прокомментируйте.

build.gradle

...
implementation "androidx.appcompat:appcompat:1.1.0-rc01"
implementation "androidx.navigation:navigation-fragment-ktx:2.0.0"
implementation "androidx.navigation:navigation-ui-ktx:2.0.0"
...

MainActivity.kt

...
import androidx.appcompat.app.AppCompatActivity
...

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        setContentView(R.layout.activity_main)

        ...

        val navController = findNavController(R.id.nav_host_fragment)
        val appBarConfiguration = AppBarConfiguration(navController.graph)

        // This line is only necessary if using the default action bar.
        setupActionBarWithNavController(navController, appBarConfiguration)

        // This remaining block is only necessary if using a Toolbar from your layout.
        val toolbar = findViewById<Toolbar>(R.id.toolbar)
        toolbar.setupWithNavController(navController, appBarConfiguration)
        // This will handle back actions initiated by the the back arrow 
        // at the start of the toolbar.
        toolbar.setNavigationOnClickListener {
            // Handle the back button event and return to override 
            // the default behavior the same way as the OnBackPressedCallback.
            // TODO(reason: handle custom back behavior here if desired.)

            // If no custom behavior was handled perform the default action.
            navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
        }
    }

    /**
     * If using the default action bar this must be overridden.
     * This will handle back actions initiated by the the back arrow 
     * at the start of the action bar.
     */
    override fun onSupportNavigateUp(): Boolean {
        // Handle the back button event and return true to override 
        // the default behavior the same way as the OnBackPressedCallback.
        // TODO(reason: handle custom back behavior here if desired.)

        // If no custom behavior was handled perform the default action.
        val navController = findNavController(R.id.nav_host_fragment)
        return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
    }
}

MyFragment.kt

...
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
...

class MyFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val onBackPressedCallback = object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                // Handle the back button event
            }
        }
        requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback)
    }
}

Официальную документацию можно посмотреть на https://developer.android.com/guide/navigation/navigation-custom-back

Ответ 8

Попробуй это. Я думаю, что это поможет вам.

override fun onBackPressed() {
    when (mNavController.getCurrentDestination()!!.getId()) {

        R.id.loginFragment -> {
            onWarningAlertDialog(this, "Alert", "Do you want to close this application ?")
        }
        R.id.registerFragment -> {
            super.onBackPressed()
        }
    }
}



private fun onWarningAlertDialog(mainActivity: MainActivity, s: String, s1: String) {

        val dialogBuilder = AlertDialog.Builder(this)
        dialogBuilder.setMessage(/*""*/s1)
                .setCancelable(false)
                .setPositiveButton("Proceed", DialogInterface.OnClickListener { dialog, id ->
                    finish()
                })
                .setNegativeButton("Cancel", DialogInterface.OnClickListener { dialog, id ->
                    dialog.cancel()
                })

        // create dialog box
        val alert = dialogBuilder.create()
        // set title for alert dialog box
        alert.setTitle("AlertDialogExample")
        // show alert dialog
        alert.show()
    }

Ответ 9

Вот мое решение

Используйте androidx.appcompat.app.AppCompatActivity для действия, которое содержит фрагмент NavHostFragment.

Определите следующий интерфейс и внедрите его во все фрагменты назначения навигации

interface InterceptionInterface {

    fun onNavigationUp(): Boolean
    fun onBackPressed(): Boolean
}

В вашей деятельности переопределите onSupportNavigateUp и onBackPressed:

override fun onSupportNavigateUp(): Boolean {
        return getCurrentNavDest().onNavigationUp() || navigation_host_fragment.findNavController().navigateUp()
}

override fun onBackPressed() {
        if (!getCurrentNavDest().onBackPressed()){
            super.onBackPressed()
        }
}

private fun getCurrentNavDest(): InterceptionInterface {
        val currentFragment = navigation_host_fragment.childFragmentManager.primaryNavigationFragment as InterceptionInterface
        return currentFragment
}

Преимущество этого решения заключается в том, что фрагментам назначения навигации не нужно беспокоиться о том, что их слушатели не будут зарегистрированы, как только они отсоединятся.

Ответ 10

Я попробовал решение Jurij Pitulja, но я просто не смог найти getOnBackPressedDispatcher или addOnBackPressedCallback, также используя решение Kiryl Tkach, не смог найти текущий фрагмент, поэтому вот мой:

interface OnBackPressedListener {
    fun onBackPressed(): Boolean
}

override fun onBackPressed() {
    val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
    val currentFragment = navHostFragment?.childFragmentManager!!.fragments[0]
    if (currentFragment !is OnBackPressedListener || !(currentFragment as OnBackPressedListener).onBackPressed()) super.onBackPressed()

таким образом, вы можете во фрагменте решить, должно ли действие взять контроль над нажатой спиной или нет.

Кроме того, у вас есть BaseActivity для всех ваших действий, вы можете реализовать так

override fun onBackPressed() {
    val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
    if (navHostFragment != null){
        val currentFragment = navHostFragment.childFragmentManager.fragments[0]
        if (currentFragment !is AuthContract.OnBackPressedListener ||
                !(currentFragment as AuthContract.OnBackPressedListener).onBackPressed()) super.onBackPressed()
    } else {
        super.onBackPressed()
    }
}

Ответ 11

Если вы используете BaseFragment для своего приложения, вы можете добавить onBackPressedDispatcher к вашему базовому фрагменту.

//Make a BaseFragment for all your fragments
abstract class BaseFragment : Fragment() {

private lateinit var callback: OnBackPressedCallback

/**
 * SetBackButtonDispatcher in OnCreate
 */

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setBackButtonDispatcher()
}

/**
 * Adding BackButtonDispatcher callback to activity
 */
private fun setBackButtonDispatcher() {
    callback = object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            onBackPressed()
        }
    }
    requireActivity().onBackPressedDispatcher.addCallback(this, callback)
}

/**
 * Override this method into your fragment to handleBackButton
 */
  open fun onBackPressed() {
  }

}

Переопределите onBackPressed() в вашем фрагменте, расширяя базовый фрагмент

//How to use this into your fragment
class MyFragment() : BaseFragment(){

private lateinit var mView: View

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    mView = inflater.inflate(R.layout.fragment_my, container, false)
    return mView.rootView
}

override fun onBackPressed() {
    //Write your code here on back pressed.
}

}

Ответ 12

Я написал в основной деятельности, как это,

override fun onSupportNavigateUp(): Boolean {
        return findNavController(R.id.my_nav_host_fragment).navigateUp(appBarConfiguration)
    }   

Ответ 13

В зависимости от вашей логики, если вы хотите закрыть только текущий фрагмент, вы должны передать viewLifecycleOwner, код показан ниже:

   requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            requireActivity().finish()
        }
    })

Однако, если вы хотите закрыть приложение на backPressed независимо от того, из какого фрагмента (вероятно, вы этого не захотите!), не передавайте viewLifecycleOwner. Также, если вы хотите отключить кнопку возврата, ничего не делайте внутри handleOnBackPressed(), см. ниже:

 requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            // do nothing it will disable the back button
        }
    })

Ответ 14

Это 2 строки кода, которые можно прослушивать при нажатии на кнопку, от фрагментов [ПРОВЕРЕНО И РАБОТАЕТ]

  requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
        @Override
        public void handleOnBackPressed() {

            //setEnabled(false); // call this to disable listener
            //remove(); // call to remove listener
            //Toast.makeText(getContext(), "Listing for back press from this fragment", Toast.LENGTH_SHORT).show();
     }

Ответ 15

Рекомендуемый метод работал для меня, но после обновления моей библиотеки implementation 'androidx.appcompat:appcompat:1.1.0'

Реализуйте, как показано ниже

 val onBackPressedCallback = object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            // Handle the back button event
        }
    }
    requireActivity().onBackPressedDispatcher.addCallback(this, onBackPressedCallback)

используя Kotlin