IllegalArgumentException: navigasyon hedefi xxx bu NavController tarafından bilinmiyor


139

Bir Parçadan diğerine gitmeye çalıştığımda yeni Android Gezinme Mimarisi bileşeniyle ilgili sorun yaşıyorum , şu garip hatayı alıyorum:

java.lang.IllegalArgumentException: navigation destination XXX
is unknown to this NavController

Bu belirli navigasyon dışında diğer tüm gezinme işlemleri iyi çalışıyor.

findNavController()Dosyasına erişmek için Fragment işlevini kullanıyorum NavController.

Herhangi bir yardım takdir edilecektir.


Lütfen daha iyi anlamak için bazı kodlar girin.
Alex

12
Bu bana da oluyor.
Eury Pérez Beltré

Şimdiye kadar bu hatanın ortaya çıkma oranı kütüphanenin daha yeni sürümleriyle azaldı, ancak kütüphanenin henüz iyi belgelenmediğini düşünüyorum.
Jerry Okafor

Yanıtlar:


76

Benim durumumda, kullanıcı aynı görünümü iki kez çok hızlı bir şekilde tıklarsa, bu çökme meydana gelecektir. Bu nedenle, birden çok hızlı tıklamayı önlemek için bir tür mantık uygulamanız gerekiyor ... Bu çok can sıkıcı, ancak gerekli gibi görünüyor.

Bunu önlemekle ilgili daha fazla bilgiyi buradan okuyabilirsiniz: Android Bir Düğmeye Çift Tıklamayı Önleme

Düzenleme 19.03.2019 : Biraz daha açıklığa kavuşturmak için, bu kilitlenme yalnızca "aynı görünüme iki kez çok hızlı bir şekilde tıklanarak" tekrarlanamaz. Alternatif olarak, yalnızca iki parmağınızı kullanabilir ve aynı anda iki (veya daha fazla) görünümü tıklatabilirsiniz; burada her görünümün gerçekleştireceği kendi gezinme işlevi vardır. Bu, özellikle bir öğe listeniz olduğunda yapmak kolaydır. Yukarıdaki çoklu tıklama önleme bilgileri bu durumu ele alacaktır.

Edit 4/16/2020 : Yukarıdaki Stack Overflow gönderisini okumakla aşırı derecede ilgilenmiyorsanız, uzun süredir kullandığım kendi (Kotlin) çözümümü dahil ediyorum.

OnSingleClickListener.kt

class OnSingleClickListener : View.OnClickListener {

    private val onClickListener: View.OnClickListener

    constructor(listener: View.OnClickListener) {
        onClickListener = listener
    }

    constructor(listener: (View) -> Unit) {
        onClickListener = View.OnClickListener { listener.invoke(it) }
    }

    override fun onClick(v: View) {
        val currentTimeMillis = System.currentTimeMillis()

        if (currentTimeMillis >= previousClickTimeMillis + DELAY_MILLIS) {
            previousClickTimeMillis = currentTimeMillis
            onClickListener.onClick(v)
        }
    }

    companion object {
        // Tweak this value as you see fit. In my personal testing this
        // seems to be good, but you may want to try on some different
        // devices and make sure you can't produce any crashes.
        private const val DELAY_MILLIS = 200L

        private var previousClickTimeMillis = 0L
    }

}

ViewExt.kt

fun View.setOnSingleClickListener(l: View.OnClickListener) {
    setOnClickListener(OnSingleClickListener(l))
}

fun View.setOnSingleClickListener(l: (View) -> Unit) {
    setOnClickListener(OnSingleClickListener(l))
}

HomeFragment.kt

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    settingsButton.setOnSingleClickListener {
        // navigation call here
    }
}

23
Aynı anda 2 parmağınızı kullanma ve 2 görünümü tıklama ile ilgili düzenleme! Bu benim için anahtar ve sorunu kolayca tekrarlamama yardımcı oldu. Bu bilgilerle harika güncelleme.
Richard Le Mesurier

Hata ayıklama aşamasında, uygulama yürütmeye devam etmek için beklerken takılıyken tıkladım. IDE'ye arka arkaya iki tıklamanın başka bir durumu gibi görünüyor
Marco

1
Bunun için teşekkürler. Beni birkaç
kazadan

58

currentDestinationNavigate'i aramadan önce kontrol et yararlı olabilir.

Örneğin, navigasyon grafiğinde iki parça hedefiniz varsa fragmentAve fragmentBve sadece bir eylem gelen var fragmentAetmek fragmentB. arama navigate(R.id.action_fragmentA_to_fragmentB), IllegalArgumentExceptionzaten açık olduğunuzda sonuçlanacaktır fragmentB. Bu nedenle currentDestinationnavigasyona başlamadan önce daima kontrol etmelisiniz .

if (navController.currentDestination?.id == R.id.fragmentA) {
    navController.navigate(R.id.action_fragmentA_to_fragmentB)
}

3
Bağımsız değişkenlerle bir eylemle gezinen bir arama uygulamam var. Böylece mevcut Hedeften kendisine gidebilir. NavController.currentDestination == navController.graph.node dışında aynısını yaptım. Yine de biraz kirli hissettim ve bunu yapmak zorunda kalmamam gerektiğini hissediyorum.
Shawn Maybush

86
Kütüphane bizi bu kontrolü yapmaya zorlamamalı, gerçekten saçma.
DaniloDeQueiroz

Ben de aynı sorunu yaşadım. Bir EditText ve EditText içeriğini veritabanında saklamak için bir 'kaydet' düğmesine sahiptim. Her zaman 'kaydet' düğmesine basıldığında çöktü. Sebebin, 'kaydet' düğmesine basabilmem için geri düğmesine dokunarak ekran klavyesinden kurtulmam gerektiğinden şüpheleniyorum.
Fox

Bu, bir hata durumunu kontrol eder, ancak sorunu çözmez. İlginç bir şekilde, bu durum, gezinme arka yığını istenmeyen nedenlerle boş hale gelirse geçerlidir.
Mike76

1
hatta iOS'ta bile, düğmeye birden çok kez bastığınızda bazen birden fazla ViewController'a basılabilir. Sanırım hem Android hem de iOS'ta bu sorun var.
coolcool1994

47

Gezinme denetleyicisinin geçerli hedefinde istenen eylemi kontrol edebilirsiniz.

UPDATE , güvenli gezinme için genel eylemlerin kullanımını ekledi.

fun NavController.navigateSafe(
        @IdRes resId: Int,
        args: Bundle? = null,
        navOptions: NavOptions? = null,
        navExtras: Navigator.Extras? = null
) {
    val action = currentDestination?.getAction(resId) ?: graph.getAction(resId)
    if (action != null && currentDestination?.id != action.destinationId) {
        navigate(resId, args, navOptions, navExtras)
    }
}

1
Bu çözüm, currentDestinationeylem listesinin dışında tanımlanan herhangi bir eylem için çalışmayacaktır . Tanımlanmış küresel bir eyleminiz olduğunu varsayalım ve gezinmek için bu eylemi kullanın. Bu başarısız olur çünkü eylem, geçerli Hedef'in <action> listesinde tanımlanmamıştır. Gibi bir kontrol eklemek currentDestination?.getAction(resId) != null || currentDestination?.id != resIdsorunu çözmelidir, ancak her durumu da kapsamayabilir.
wchristiansen

@wchristiansen, notlar için teşekkürler. Kodu genel eylemlerin kullanımıyla güncelledim
Alex Nuts

@AlexNuts harika cevap. Sanırım kaldırabilirsiniz ?: graph.getAction(resId)-> currentDestination?.getAction(resId)hem Global hem de Global olmayan eylemler için bir eylem döndürecektir (test ettim). Yerine geçmek> - Güvenli Args yararlandı Ayrıca, daha iyi olurdu navDirections: NavDirectionsdaha resIdve argsayrı ayrı.
Wess

@AlexNuts Bu çözümün mevcut hedefle aynı hedefe gitmeyi desteklemediğini unutmayın. Y Paketi ile X hedefinden Z Paketi ile X hedefine geçiş mümkün değildir.
Wess

18

Ayrıca, B Parçalarının ViewPager'ına sahip bir A Parçası'na sahipseniz ve B'den C'ye gitmeye çalışırsanız da olabilir

ViewPager'da parçalar A'nın hedefi olmadığından, grafiğiniz B'de olduğunuzu bilmez.

C'ye gitmek için B'deki ADirections'ı kullanmak bir çözüm olabilir.


Bu durumda, çarpışma her seferinde olmaz, ancak nadiren olur. Nasıl çözeceksin?
Srikar Reddy

NavGraph'ın içine genel bir eylem ekleyebilir ve gezinmek için kullanabilirsiniz
Abraham Mathew

1
B'nin ebeveyninin tam olarak farkında olması gerekmediğinden, ADirections'ı gibi bir arayüz aracılığıyla kullanmak daha iyi olacaktır (parentFragment as? XActionListener)?.Xaction()ve eğer yardımcı olursa bu işlevi yerel bir değişken olarak tutabileceğinizi unutmayın
hmac

aynı sorunu
yaşadığım

herhangi biri örnek bir kodu verebilir, ben de aynı konuya takılı kaldım. Bir fragman ve ardından bir tabfragment alın
Usman Zafer

13

Kazayı önlemek için yaptığım şey şuydu:

Bir BaseFragment'ım var, oraya aşağıdakiler tarafından bilindiğinden funemin olmak için bunu ekledim :destinationcurrentDestination

fun navigate(destination: NavDirections) = with(findNavController()) {
    currentDestination?.getAction(destination.actionId)
        ?.let { navigate(destination) }
}

SafeArgs eklentisini kullandığımı belirtmek gerekir .


12

Benim durumumda yukarı gezinmek için özel bir geri düğmesi kullanıyordum. onBackPressed()Aşağıdaki kod yerine aradım

findNavController(R.id.navigation_host_fragment).navigateUp()

Bu oluşmasına neden oldu IllegalArgumentException. Bunun navigateUp()yerine yöntemi kullanmak için değiştirdikten sonra , tekrar bir çökme yaşamadım.


OnBackPressed ile bu arasındaki farkın ne olduğunu anlayamıyorum, hala sistem geri düğmesine takılı kalıyor ve onu geçersiz kılıyor ve bununla değiştirmek çılgınca görünüyor
Daniel Wilson

2
Bunun çılgınca göründüğüne katılıyorum. Android navigasyon mimarisi bileşeninde karşılaştığım şeylerin çoğu biraz çılgınca geliyor, çok katı bir IMO kurdu. Çok fazla baş ağrısı yarattığı için projemiz için kendi uygulamamı yapmayı düşünüyorum
Neil

Benim için çalışmıyor ... Hala aynı hatayı alıyorum.
Otziii

6

TL; DRnavigate Aramalarınızı try-catch(basit yol) ile tamamlayın veya navigatekısa süre içinde yalnızca bir arama olacağından emin olun . Bu sorun muhtemelen ortadan kalkmayacak. Uygulamanızda daha büyük kod parçacığını kopyalayın ve deneyin.

Merhaba. Yukarıdaki birkaç yararlı yanıta dayanarak, genişletilebilecek çözümümü paylaşmak istiyorum.

İşte uygulamamdaki bu çökmeye neden olan kod:

@Override
public void onListItemClicked(ListItem item) {
    Bundle bundle = new Bundle();
    bundle.putParcelable(SomeFragment.LIST_KEY, item);
    Navigation.findNavController(recyclerView).navigate(R.id.action_listFragment_to_listItemInfoFragment, bundle);
}

Hatayı kolayca yeniden oluşturmanın bir yolu, her bir öğeye tıklamanın yeni ekranda gezinmede çözüldüğü öğeler listesine birden fazla parmakla dokunmaktır (temelde insanların belirttiği gibi - çok kısa bir süre içinde iki veya daha fazla tıklama) ). Onu farkettim:

  1. İlk navigateçağrı her zaman iyi çalışır;
  2. İkinci ve navigateyöntemin diğer tüm çağrıları çözülür IllegalArgumentException.

Benim açımdan bu durum çok sık karşımıza çıkabilir. Kodun tekrarlanması kötü bir uygulama olduğundan ve bir etki noktasına sahip olmak her zaman iyidir çünkü bir sonraki çözümü düşündüm:

public class NavigationHandler {

public static void navigate(View view, @IdRes int destination) {
    navigate(view, destination, /* args */null);
}

/**
 * Performs a navigation to given destination using {@link androidx.navigation.NavController}
 * found via {@param view}. Catches {@link IllegalArgumentException} that may occur due to
 * multiple invocations of {@link androidx.navigation.NavController#navigate} in short period of time.
 * The navigation must work as intended.
 *
 * @param view        the view to search from
 * @param destination destination id
 * @param args        arguments to pass to the destination
 */
public static void navigate(View view, @IdRes int destination, @Nullable Bundle args) {
    try {
        Navigation.findNavController(view).navigate(destination, args);
    } catch (IllegalArgumentException e) {
        Log.e(NavigationHandler.class.getSimpleName(), "Multiple navigation attempts handled.");
    }
}

}

Ve böylece yukarıdaki kod bundan sadece bir satırda değişir:

Navigation.findNavController(recyclerView).navigate(R.id.action_listFragment_to_listItemInfoFragment, bundle);

buna:

NavigationHandler.navigate(recyclerView, R.id.action_listFragment_to_listItemInfoFragment, bundle);

Hatta biraz daha kısaldı. Kod, kazanın meydana geldiği yerde test edildi. Artık deneyimlemedi ve aynı hatayı daha fazla önlemek için diğer navigasyonlarda aynı çözümü kullanacak.

Herhangi bir düşünceye açığız!

Kazaya tam olarak ne sebep olur

Yöntemi kullandığımızda burada aynı gezinme grafiği, gezinme denetleyicisi ve arka yığınla çalıştığımızı unutmayın Navigation.findNavController.

Burada hep aynı denetleyiciyi ve grafiği elde ederiz. Ne zaman navigate(R.id.my_next_destination)grafiği ve arka yığını değiştirir denir neredeyse anında UI henüz güncellenmemiş ise. Yeterince hızlı değil, ama sorun değil. Arka yığın değiştikten sonra, navigasyon sistemi ikinci navigate(R.id.my_next_destination)çağrıyı alır . Arka yığın değiştiğinden, artık yığındaki en üst parçaya göre çalışıyoruz. En üstteki parça, kullanarak gezindiğiniz parçadır R.id.my_next_destination, ancak kimliğe sahip sonraki herhangi bir hedefi içermez R.id.my_next_destination. Böylece IllegalArgumentExceptionparçanın hakkında hiçbir şey bilmediği kimlik sayesinde elde edersiniz .

Bu kesin hata NavController.javayöntemde bulunabilir findDestination.


4

Benim durumumda sorun, Parçalarımdan birini .pdf dosyasının viewpagerçocuğu olarak bir parçanın içinde yeniden kullandığımda meydana geldi viewpager. viewpager(Ana fragmanı) Fragman Gezinti XML ilave edildi, ancak eylem içinde eklenmedi viewpagerana fragmanı.

nav.xml
//reused fragment
<fragment
    android:id="@+id/navigation_to"
    android:name="com.package.to_Fragment"
    android:label="To Frag"
    tools:layout="@layout/fragment_to" >
    //issue got fixed when i added this action to the viewpager parent also
    <action android:id="@+id/action_to_to_viewall"
        app:destination="@+id/toViewAll"/>
</fragment>
....
// viewpager parent fragment
<fragment
    android:id="@+id/toViewAll"
    android:name="com.package.ViewAllFragment"
    android:label="to_viewall_fragment"
    tools:layout="@layout/fragment_view_all">

Eylemi üst görüntüleyici parçasına da aşağıda gösterildiği gibi ekleyerek sorun düzeltildi:

nav.xml
//reused fragment
<fragment
    android:id="@+id/navigation_to"
    android:name="com.package.to_Fragment"
    android:label="To Frag"
    tools:layout="@layout/fragment_to" >
    //issue got fixed when i added this action to the viewpager parent also
    <action android:id="@+id/action_to_to_viewall"
        app:destination="@+id/toViewAll"/>
</fragment>
....
// viewpager parent fragment
<fragment
    android:id="@+id/toViewAll"
    android:name="com.package.ViewAllFragment"
    android:label="to_viewall_fragment"
    tools:layout="@layout/fragment_view_all"/>
    <action android:id="@+id/action_to_to_viewall"
        app:destination="@+id/toViewAll"/>
</fragment>

4

Bugün

def navigationVersion = "2.2.1"

Sorun hala var. Kotlin'e yaklaşımım:

// To avoid "java.lang.IllegalArgumentException: navigation destination is unknown to this NavController", se more https://stackoverflow.com/q/51060762/6352712
fun NavController.navigateSafe(
    @IdRes destinationId: Int,
    navDirection: NavDirections,
    callBeforeNavigate: () -> Unit
) {
    if (currentDestination?.id == destinationId) {
        callBeforeNavigate()
        navigate(navDirection)
    }
}

fun NavController.navigateSafe(@IdRes destinationId: Int, navDirection: NavDirections) {
    if (currentDestination?.id == destinationId) {
        navigate(navDirection)
    }
}

4

Navigasyona başlamadan önce, navigasyonu talep eden Fragment'in bu ana başlıktan alınmış mevcut hedef olup olmadığını kontrol edebilirsiniz .

Temel olarak, daha sonra aramak için parça üzerinde bir etiket belirler.

/**
 * Returns true if the navigation controller is still pointing at 'this' fragment, or false if it already navigated away.
 */
fun Fragment.mayNavigate(): Boolean {

    val navController = findNavController()
    val destinationIdInNavController = navController.currentDestination?.id
    val destinationIdOfThisFragment = view?.getTag(R.id.tag_navigation_destination_id) ?: destinationIdInNavController

    // check that the navigation graph is still in 'this' fragment, if not then the app already navigated:
    if (destinationIdInNavController == destinationIdOfThisFragment) {
        view?.setTag(R.id.tag_navigation_destination_id, destinationIdOfThisFragment)
        return true
    } else {
        Log.d("FragmentExtensions", "May not navigate: current destination is not the current fragment.")
        return false
    }
}

R.id.tag_navigation_destination_id benzersiz olduğundan emin olmak için ids.xml dosyanıza eklemeniz gereken bir kimliktir. <item name="tag_navigation_destination_id" type="id" />

Hata ve çözüm hakkında daha fazla bilgi ve "Korkunç olanın düzeltilmesi"navigateSafe(...) içindeki genişletme yöntemleri bu NavController tarafından bilinmiyor "


Bu soruna birkaç farklı çözüm üzerinde çalıştım ve sizinki kesinlikle en iyisi. Bu kadar az sevgi görmek beni üzüyor
Luka


evet, cevabı güncelledim
Frank

Etiket nereden geliyor ve neden gerekli? Gezinme bileşenindeki gerçek kimliklerin bunlarla eşleşmediği sorunlarım var R.id.
riezebosch

R.id.tag_navigation_destination_idbenzersiz olduğundan emin olmak için ids.xml dosyanıza eklemeniz gereken bir kimliktir. <item name="tag_navigation_destination_id" type="id" />
Frank

3

Benim durumumda, birden fazla nav grafik dosyam vardı ve 1 nav grafik konumundan başka bir nav grafiğindeki bir hedefe geçmeye çalışıyordum.

Bunun için 1. nav grafiğini şöyle eklemeliyiz

<include app:graph="@navigation/included_graph" />

ve bunu eyleminize ekleyin:

<action
        android:id="@+id/action_fragment_to_second_graph"
        app:destination="@id/second_graph" />

nerede second_graph:

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/second_graph"
    app:startDestination="@id/includedStart">

ikinci grafikte.

Daha fazla bilgi burada


3

Anında kontrole tıklamak için standart kod yerine gezinmeden önce kontrol koyarak aynı sorunu çözdüm

 if (findNavController().currentDestination?.id == R.id.currentFragment) {
        findNavController().navigate(R.id.action_current_next)}
/* Here R.id.currentFragment is the id of current fragment in navigation graph */

bu cevaba göre

https://stackoverflow.com/a/56168225/7055259


2

Benim durumumda hata , açılış ekranından sonra Single Topve Clear Taskseçenekleriyle bir gezinme eylemim olduğu için ortaya çıktı .


1
Ancak clearTask kullanımdan kaldırıldı, bunun yerine popUpTo () kullanmalısınız.
Jerry Okafor

@ Po10cio Bu bayrakların hiçbirine ihtiyaç duyulmadı, sadece kaldırdım ve düzeltildi.
Eury Pérez Beltré

2

Aynı hatayı aldım çünkü Gezinme Çekmecesi kullandım ve getSupportFragmentManager().beginTransaction().replace( )aynı zamanda kodumun bir yerinde.

Bu koşulu kullanarak hatadan kurtuldum (hedefin olup olmadığını test ettim):

if (Navigation.findNavController(v).getCurrentDestination().getId() == R.id.your_destination_fragment_id)
Navigation.findNavController(v).navigate(R.id.your_action);

Benim durumumda, gezinme çekmecesi seçeneklerine tıkladığımda önceki hata tetiklendi. Temel olarak yukarıdaki kod hatayı gizledi, çünkü kodumda bir yerde getSupportFragmentManager().beginTransaction().replace( )koşulu kullanarak navigasyonu kullandım -

 if (Navigation.findNavController(v).getCurrentDestination().getId() ==
  R.id.your_destination_fragment_id) 

asla ulaşılamadı çünkü (Navigation.findNavController(v).getCurrentDestination().getId()her zaman ev parçasına işaret ediyordu. Navigation.findNavController(v).navigate(R.id.your_action)Tüm gezinme eylemleriniz için yalnızca veya gezinme grafiği denetleyici işlevlerini kullanmalısınız .



1

Bu istisnayı sınıfların bazı isimlerini değiştirdikten sonra yakaladım. Örneğin: Aradım sınıflar vardı FragmentAile @+is/fragment_anavigasyon grafikte ve FragmentBile @+id/fragment_b. Sonra sildim FragmentAve adını FragmentBdeğiştirdim FragmentA. Yani bunun düğüm sonra FragmentAhala navigasyon grafiğinde kaldı ve android:namebir FragmentBbireyin düğümü değiştirildi path.to.FragmentA. Aynı android:nameve farklı iki düğümüm vardı android:idve ihtiyacım olan eylem kaldırılan sınıfın düğümünde tanımlandı.


1

Geri düğmesine iki kez bastığımda aklıma geliyor. İlk başta, keserim KeyListenerve geçersiz kılırım KeyEvent.KEYCODE_BACK. Aşağıdaki kodu OnResumeFragment adı verilen fonksiyona ekledim ve ardından bu soru / sorun çözüldü.

  override fun onResume() {
        super.onResume()
        view?.isFocusableInTouchMode = true
        view?.requestFocus()
        view?.setOnKeyListener { v, keyCode, event ->
            if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_BACK) {
                activity!!.finish()
                true
            }
            false
        }
    }

Bana ikinci kez olduğunda ve durumu ilkiyle aynı olduğunda, belki adsurdişlevi kullandığımı fark ettim . Bu durumları analiz edelim.

  1. Önce FragmentA, FragmentB'ye gider, ardından FragmentB, FragmentA'ya gider, sonra geri düğmesine basın ... çökme belirir.

  2. İkinci olarak, FragmentA, FragmentB'ye gider, ardından FragmentB, FragmentC'ye gider, FragmentC, FragmentA'ya gider, sonra geri düğmesine basın ... çarpışma belirir.

Bu yüzden geri düğmesine bastığınızda FragmentA'nın FragmentB veya FragmentC'ye döneceğini ve ardından giriş karmaşasına neden olacağını düşünüyorum. Son olarak, adlandırılmış işlevin popBackStackgezinmek yerine geri için kullanılabileceğini buldum .

  NavHostFragment.findNavController(this@TeacherCloudResourcesFragment).
                        .popBackStack(
                            R.id.teacher_prepare_lesson_main_fragment,false
                        )

Şimdiye kadar sorun gerçekten çözüldü.


1

Görünüşe göre backstack'in fragmentManager kontrolü ve backstack'in Navigasyon Mimarisi kontrolü de bu soruna neden olabilir.

Örneğin, orijinal CameraX temel örneği aşağıdaki gibi fragmentManager arka yığın navigasyonunu kullandı ve Navigasyon ile doğru şekilde etkileşime girmemiş gibi görünür:

// Handle back button press
        view.findViewById<ImageButton>(R.id.back_button).setOnClickListener {
            fragmentManager?.popBackStack()
        }

Ana parçadan (bu durumda kamera parçası) hareket etmeden önce 'mevcut hedefi' bu sürümle günlüğe kaydederseniz ve ardından ana parçaya geri döndüğünüzde yeniden günlüğe kaydederseniz, günlüklerdeki id'den kimliğin aynı değil. Tahminen, Navigasyon parçaya taşınırken onu güncelledi ve fragmntManager geri giderken onu tekrar güncellemedi. Günlüklerden:

Önce : D / CameraXBasic: currentDest ?: androidx.navigation.fragment.FragmentNavigator$Destination@b713195

Sonra : D / CameraXBasic: currentDest ?: androidx.navigation.fragment.FragmentNavigator$Destination@9807d8f

CameraX temel örneğinin güncellenmiş sürümü, aşağıdaki gibi geri dönmek için Navigasyon kullanır:

 // Handle back button press
        view.findViewById<ImageButton>(R.id.back_button).setOnClickListener {
            Navigation.findNavController(requireActivity(), R.id.fragment_container).navigateUp()
        }

Bu doğru çalışır ve günlükler ana parçaya döndüğünde aynı kimliği gösterir.

Önce : D / CameraXBasic: currentDest ?: androidx.navigation.fragment.FragmentNavigator$Destination@b713195

Sonra : D / CameraXBasic: currentDest ?: androidx.navigation.fragment.FragmentNavigator$Destination@b713195

Hikayenin ahlaki yönünün, en azından şu anda, Navigasyon ile fragmentManager navigasyonunu karıştırırken çok dikkatli olması olduğundan şüpheleniyorum.


Bu mantıklı geliyor, daha fazla araştıracağım. Bu iddiayı doğrulayabilen veya kanıtlayabilen var mı?
Jerry Okafor

@JerryOkafor - CameraX Örneğine dayanarak üzerinde çalıştığım bir uygulamada test ettim ve doğruladım ancak başka birinin de bunu görüp görmediğini görmek iyi olur. Aslında aynı uygulamada bir yerde 'geri navigasyonu' kaçırdım, bu yüzden yakın zamanda tekrar düzelttim.
Mick

1

Saçma ama çok güçlü bir yol: Şunu basitçe adlandırın:

view?.findNavController()?.navigateSafe(action)

Sadece Bu Uzantıyı Oluşturun:

fun NavController.navigateSafe(
    navDirections: NavDirections? = null
) {
    try {
        navDirections?.let {
            this.navigate(navDirections)
        }
    }
    catch (e:Exception)
    {
        e.printStackTrace()
    }
}

1

Bu sorunun birçok nedeni olabilir. Benim durumumda MVVM modelini kullanıyordum ve boole doğru olduğunda navigasyon için bir boole gözlemliyordum -> başka bir yerde gezin, hiçbir şey yapmayın ve bu iyi çalışıyordu ama burada bir hata vardı

Hedef parçadan geri düğmesine bastığımda aynı problemle karşılaşıyordum. Ve boolean değerini false olarak değiştirmeyi unuttuğum için boolean nesnesiydi, bu karışıklık yarattı. viewModel'de değerini false olarak değiştirmek için bir işlev yarattım ve findNavController () öğesinden hemen sonra çağırdı


1

Genellikle bu benim başıma geldiğinde, Charles Madere tarafından açıklanan sorunu yaşadım: Aynı kullanıcı arabiriminde iki gezinme olayı tetiklendi, biri geçerli Hedefi değiştiriyor ve diğeri mevcut Hedef değiştiği için başarısız oluyor. Bu, findNavController.navigate'i çağıran bir tıklama dinleyici ile iki görünüme çift dokunursanız veya tıklarsanız olabilir.

Bu nedenle, bu sorunu çözmek için ya if-checks, try-catch kullanabilirsiniz ya da ilgileniyorsanız, gezinmeden önce bu kontrolü sizin için yapan bir findSafeNavController () vardır. Ayrıca, bu sorunu unutmadığınızdan emin olmak için bir tüy kontrolü de vardır.

GitHub

Konuyu detaylandıran makale


1

Çok hızlı tıklarsanız, boşa ve çökmeye neden olur.

Bu konuda yardımcı olması için RxBinding lib kullanabiliriz. Tıklama gerçekleşmeden önce kısma ve süre ekleyebilirsiniz.

 RxView.clicks(view).throttleFirst(duration, TimeUnit.MILLISECONDS)
            .subscribe(__ -> {
            });

Android'de kısıtlama hakkındaki bu makaleler yardımcı olabilir. Şerefe!


1

Bir geri dönüşüm görünümü kullanıyorsanız, tıklamanıza ve ayrıca geri dönüşüm görünüm xml dosyası kullanımınıza bir tıklama dinleyici soğuma süresi ekleyin. android:splitMotionEvents="false"


1
Aşağıdaki cevaplara bakın
Çılgın

1

Ian Lake'in bu twitter başlığındaki tavsiyesini düşündükten sonra aşağıdaki yaklaşımı buldum. Böyle NavControllerWrappertanımladıktan sonra :

class NavControllerWrapper constructor(
  private val navController: NavController
) {

  fun navigate(
    @IdRes from: Int,
    @IdRes to: Int
  ) = navigate(
    from = from,
    to = to,
    bundle = null
  )

  fun navigate(
    @IdRes from: Int,
    @IdRes to: Int,
    bundle: Bundle?
  ) = navigate(
    from = from,
    to = to,
    bundle = bundle,
    navOptions = null,
    navigatorExtras = null
  )

  fun navigate(
    @IdRes from: Int,
    @IdRes to: Int,
    bundle: Bundle?,
    navOptions: NavOptions?,
    navigatorExtras: Navigator.Extras?
  ) {
    if (navController.currentDestination?.id == from) {
      navController.navigate(
        to,
        bundle,
        navOptions,
        navigatorExtras
      )
    }
  }

  fun navigate(
    @IdRes from: Int,
    directions: NavDirections
  ) {
    if (navController.currentDestination?.id == from) {
      navController.navigate(directions)
    }
  }

  fun navigateUp() = navController.navigateUp()

  fun popBackStack() = navController.popBackStack()
}

Ardından navigasyon kodunda:

val navController = navControllerProvider.getNavController()
navController.navigate(from = R.id.main, to = R.id.action_to_detail)

0

Bu bana oldu, sorunum bir FAB'a tıklamaktı tab item fragment. Sekme öğesi parçalarının birinden konumuna gitmeye çalışıyordum another fragment.

Ama uygun Ian Gölü içinde bu cevap biz kullanmak zorunda tablayoutve viewpager, hiçbir navigasyon bileşeni desteği . Bu nedenle, parça içeren sekme düzeninden sekme öğesi parçasına kadar gezinme yolu yoktur.

örn:

containing fragment -> tab layout fragment -> tab item fragment -> another fragment

Çözüm, parçayı içeren sekme düzeninden amaçlanan parçaya bir yol oluşturmaktı örn: yol: container fragment -> another fragment

dezavantajı:

  • Gezinme grafiği artık kullanıcı akışını doğru şekilde temsil etmiyor.

0

Benim durumumda, vakaların% 50'sinde başka bir iş parçacığından geçmeye çalıştığımda bu hatayı aldım. Kodu ana iş parçacığı üzerinde çalıştırmak yardımcı olur

requireActivity().runOnUiThread {
    findNavController().navigate(...)
}

Bu konuda daha fazla oy görmek istiyorum, mantıklı geliyor ama doğrulayamıyorum.
Jerry Okafor

0

Benim durumumda bu, yanlışlıkla bir +eylem içi hedef eklediğimde meydana geldi ve kilitlenme yalnızca aynı parçaya birkaç kez gittiğimde meydana geldi.

 <action
        android:id="@+id/action_to_profileFragment"
        app:destination="@+id/profileFragment" />

Çözüm, +eylem hedefinden kaldırmak , @id/profileFragmentbunun yerine yalnızca kullanın@+id/profileFragment

 <action
        android:id="@+id/action_to_profileFragment"
        app:destination="@id/profileFragment" />

0

@Alex Nuts çözümü güncellendi

Belirli bir parça için herhangi bir eylem yoksa ve parçaya gitmek istiyorsanız

fun NavController.navigateSafe(
@IdRes actionId: Int, @IdRes fragmentId: Int, args: Bundle? = null,
navOptions: NavOptions? = null, navExtras: Navigator.Extras? = null) 
{
  if (actionId != 0) {
      val action = currentDestination?.getAction(actionId) ?: graph.getAction(actionId)
      if (action != null && currentDestination?.id != action.destinationId) {
          navigate(actionId, args, navOptions, navExtras)
    }
    } else if (fragmentId != 0 && fragmentId != currentDestination?.id)
        navigate(fragmentId, args, navOptions, navExtras)
}

0

Bu uzantıları yazdım

fun Fragment.navigateAction(action: NavDirections) {
    val navController = this.findNavController()
    if (navController.currentDestination?.getAction(action.actionId) == null) {
        return
    } else {
        navController.navigate(action)
    }
}

0

Fragment için bu uzantı işlevini oluşturdum:

fun Fragment.safeNavigate(
    @IdRes actionId: Int,
    @Nullable args: Bundle? = null,
    @Nullable navOptions: NavOptions? = null,
    @Nullable navigatorExtras: Navigator.Extras? = null
) {
    NavHostFragment.findNavController(this).apply {
        if (currentDestination?.label == this@safeNavigate::class.java.simpleName) {
            navigate(actionId, args, navOptions, navigatorExtras)
        }
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.