#android #android-architecture-navigation
Вопрос:
У меня есть два вида деятельности, в одном из которых используется навигационный график, а в другом-нет. Как я могу перейти к фрагменту навигационного графика из действия, в котором не используется NavController?
Я пытаюсь перейти от ImportMonsterActivity (после добавления новой сущности в БД) к фрагменту EditMonsterFragment в навигационном графике MainActivity.
Я думаю, что я должен быть в состоянии создать обычное намерение и дать ему некоторые дополнительные возможности, чтобы указать, куда идти в навигационном графике, но я не нашел никакой документации по этому виду навигации. Все либо использует глубокую ссылку из другого приложения, либо перемещается по навигационному графику.
Если мне нужно добавить глубокую ссылку на мой график, могу ли я сделать это без использования http? Я не хочу, чтобы этому приложению требовался доступ в Интернет, если это возможно. Я бы хотел, чтобы люди могли просто импортировать файлы, которые они загрузили или скопировали на устройство.
AndroidManifest.xml
<activity
android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<nav-graph android:value="@navigation/mobile_navigation" />
</activity>
<activity
android:name=".ImportMonsterActivity"
android:icon="@mipmap/ic_launcher"
android:label="Import Monster"
android:launchMode="singleTask"
android:priority="50">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
</activity>
mobile_navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<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/mobile_navigation"
app:startDestination="@ id/navigation_dashboard">
<fragment
android:id="@ id/navigation_search"
android:name="com.majinnaibu.monstercards.ui.search.SearchFragment"
android:label="@string/title_search"
tools:layout="@layout/fragment_search">
<action
android:id="@ id/action_navigation_search_to_navigation_monster"
app:destination="@id/navigation_monster" />
</fragment>
<fragment
android:id="@ id/navigation_dashboard"
android:name="com.majinnaibu.monstercards.ui.dashboard.DashboardFragment"
android:label="@string/title_dashboard"
tools:layout="@layout/fragment_dashboard">
<action
android:id="@ id/action_navigation_dashboard_to_navigation_monster"
app:destination="@id/navigation_monster" />
</fragment>
<fragment
android:id="@ id/navigation_collections"
android:name="com.majinnaibu.monstercards.ui.collections.CollectionsFragment"
android:label="@string/title_collections"
tools:layout="@layout/fragment_collections">
<action
android:id="@ id/action_navigation_collections_to_navigation_monster"
app:destination="@id/navigation_monster" />
</fragment>
<fragment
android:id="@ id/navigation_library"
android:name="com.majinnaibu.monstercards.ui.library.LibraryFragment"
android:label="@string/title_library"
tools:layout="@layout/fragment_library">
<action
android:id="@ id/action_navigation_library_to_navigation_monster"
app:destination="@id/navigation_monster" />
</fragment>
<fragment
android:id="@ id/navigation_monster"
android:name="com.majinnaibu.monstercards.ui.monster.MonsterDetailFragment"
android:label="Monster"
tools:layout="@layout/fragment_monster">
<argument
android:name="monster_id"
app:argType="string" />
<action
android:id="@ id/action_navigation_monster_to_editMonsterFragment"
app:destination="@id/edit_monster_navigation" />
</fragment>
<navigation
android:id="@ id/edit_monster_navigation"
app:startDestination="@id/editMonsterFragment">
<argument
android:name="monster_id"
app:argType="string" />
<fragment
android:id="@ id/editMonsterFragment"
android:name="com.majinnaibu.monstercards.ui.editmonster.EditMonsterFragment"
android:label="Edit Monster"
tools:layout="@layout/fragment_edit_monster">
<argument
android:name="monster_id"
app:argType="string" />
<action
android:id="@ id/action_editMonsterFragment_to_editBasicInfoFragment"
app:destination="@id/editBasicInfoFragment" />
<action
android:id="@ id/action_editMonsterFragment_to_editArmorFragment"
app:destination="@id/editArmorFragment" />
<action
android:id="@ id/action_editMonsterFragment_to_editSpeedFragment"
app:destination="@id/editSpeedFragment" />
<action
android:id="@ id/action_editMonsterFragment_to_editAbilityScoresFragment"
app:destination="@id/editAbilityScoresFragment" />
<action
android:id="@ id/action_editMonsterFragment_to_editSavingThrowsFragment"
app:destination="@id/editSavingThrowsFragment" />
<action
android:id="@ id/action_editMonsterFragment_to_editChallengeRatingFragment"
app:destination="@id/editChallengeRatingFragment" />
<action
android:id="@ id/action_editMonsterFragment_to_editSkillsFragment"
app:destination="@id/editSkillsFragment" />
<action
android:id="@ id/action_editMonsterFragment_to_editLanguagesFragment"
app:destination="@id/editLanguagesFragment" />
<action
android:id="@ id/action_editMonsterFragment_to_editTraitListFragment"
app:destination="@id/editTraitListFragment" />
<action
android:id="@ id/action_editMonsterFragment_to_editStringsFragment"
app:destination="@id/editStringsFragment" />
</fragment>
<fragment
android:id="@ id/editBasicInfoFragment"
android:name="com.majinnaibu.monstercards.ui.editmonster.EditBasicInfoFragment"
android:label="fragment_edit_basic_info"
tools:layout="@layout/fragment_edit_basic_info" />
<fragment
android:id="@ id/editArmorFragment"
android:name="com.majinnaibu.monstercards.ui.editmonster.EditArmorFragment"
android:label="fragment_edit_armor"
tools:layout="@layout/fragment_edit_armor" />
<fragment
android:id="@ id/editSpeedFragment"
android:name="com.majinnaibu.monstercards.ui.editmonster.EditSpeedFragment"
android:label="fragment_edit_speed"
tools:layout="@layout/fragment_edit_speed" />
<fragment
android:id="@ id/editAbilityScoresFragment"
android:name="com.majinnaibu.monstercards.ui.editmonster.EditAbilityScoresFragment"
android:label="EditAbilityScoresFragment" />
<fragment
android:id="@ id/editSavingThrowsFragment"
android:name="com.majinnaibu.monstercards.ui.editmonster.EditSavingThrowsFragment"
android:label="fragment_edit_saving_throws"
tools:layout="@layout/fragment_edit_saving_throws" />
<fragment
android:id="@ id/editChallengeRatingFragment"
android:name="com.majinnaibu.monstercards.ui.editmonster.EditChallengeRatingFragment"
android:label="fragment_edit_challenge_rating"
tools:layout="@layout/fragment_edit_challenge_rating" />
<fragment
android:id="@ id/editSkillsFragment"
android:name="com.majinnaibu.monstercards.ui.editmonster.EditSkillsFragment"
android:label="fragment_edit_skills_list"
tools:layout="@layout/fragment_edit_skills_list">
<action
android:id="@ id/action_editSkillsFragment_to_editSkillFragment"
app:destination="@id/editSkillFragment" />
</fragment>
<fragment
android:id="@ id/editSkillFragment"
android:name="com.majinnaibu.monstercards.ui.editmonster.EditSkillFragment"
android:label="fragment_edit_skill"
tools:layout="@layout/fragment_edit_skill">
<argument
android:name="name"
app:argType="string" />
<argument
android:name="abilityScore"
app:argType="com.majinnaibu.monstercards.data.enums.AbilityScore" />
<argument
android:name="proficiency"
app:argType="com.majinnaibu.monstercards.data.enums.ProficiencyType" />
<argument
android:name="advantage"
app:argType="com.majinnaibu.monstercards.data.enums.AdvantageType" />
</fragment>
<fragment
android:id="@ id/editLanguagesFragment"
android:name="com.majinnaibu.monstercards.ui.editmonster.EditLanguagesFragment"
android:label="fragment_edit_languages_list"
tools:layout="@layout/fragment_edit_languages_list">
<action
android:id="@ id/action_editLanguagesFragment_to_editLanguageFragment"
app:destination="@id/editLanguageFragment" />
</fragment>
<fragment
android:id="@ id/editLanguageFragment"
android:name="com.majinnaibu.monstercards.ui.editmonster.EditLanguageFragment"
android:label="fragment_edit_language"
tools:layout="@layout/fragment_edit_language">
<argument
android:name="name"
app:argType="string" />
<argument
android:name="canSpeak"
app:argType="boolean" />
</fragment>
<fragment
android:id="@ id/editTraitFragment"
android:name="com.majinnaibu.monstercards.ui.editmonster.EditTraitFragment"
android:label="EditTraitFragment">
<argument
android:name="description"
app:argType="string" />
<argument
android:name="name"
app:argType="string" />
<argument
android:name="traitType"
app:argType="com.majinnaibu.monstercards.data.enums.TraitType" />
</fragment>
<fragment
android:id="@ id/editTraitListFragment"
android:name="com.majinnaibu.monstercards.ui.editmonster.EditTraitsFragment"
android:label="EditTraitListFragment">
<action
android:id="@ id/action_editTraitListFragment_to_editTraitFragment"
app:destination="@id/editTraitFragment" />
<argument
android:name="traitType"
app:argType="com.majinnaibu.monstercards.data.enums.TraitType" />
</fragment>
<fragment
android:id="@ id/editStringsFragment"
android:name="com.majinnaibu.monstercards.ui.editmonster.EditStringsFragment"
android:label="EditStringsFragment">
<action
android:id="@ id/action_editStringsFragment_to_editStringFragment"
app:destination="@id/editStringFragment" />
<argument
android:name="stringType"
app:argType="com.majinnaibu.monstercards.data.enums.StringType" />
</fragment>
<fragment
android:id="@ id/editStringFragment"
android:name="com.majinnaibu.monstercards.ui.editmonster.EditStringFragment"
android:label="EditStringFragment">
<argument
android:name="stringType"
app:argType="com.majinnaibu.monstercards.data.enums.StringType" />
<argument
android:name="value"
app:argType="string" />
</fragment>
</navigation>
</navigation>
This method in ImportMonsterActivity leaves me at in a new instance of the ImportMonsterActivity with no parameters/extras/args.
private void navigateToEditMonster(UUID monsterId) {
Logger.logUnimplementedFeature(String.format("navigate to editing the monster %s", monsterId));
NavDeepLinkBuilder builder = new NavDeepLinkBuilder(this);
Bundle args = new Bundle();
args.putString("monster_id", monsterId.toString());
PendingIntent pi = builder.setGraph(R.navigation.mobile_navigation).setDestination(R.id.edit_monster_navigation).setArguments(args).createPendingIntent();
try {
pi.send(); // This line is executed
} catch (PendingIntent.CanceledException e) {
e.printStackTrace(); // This exception is not thrown
}
}
Update: I’ve tried replacing the second activity with a fragment in the nav graph and adding a deep link to open it for share and view actions, but I’m getting build errors unless I give the deep link an app:uri
. When I do set the uri I get an app crash when the intent tries to open my activity.
build error with no uri
Execution failed for task ':app:extractDeepLinksDebug'.
> Navigation XML document <deepLink> element must contain a app:uri attribute.
build error with an empty uri
Execution failed for task ':app:extractDeepLinksDebug'.
> java.net.URISyntaxException: Expected authority at index 2: //
mobile_navigation.xml
<?xml version="1.0" encoding="utf-8"?>
<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/mobile_navigation"
app:startDestination="@ id/navigation_dashboard">
<!-- unrelated fragments -->
<fragment
android:id="@ id/navigation_library"
android:name="com.majinnaibu.monstercards.ui.library.LibraryFragment"
android:label="@string/title_library"
tools:layout="@layout/fragment_library">
<action
android:id="@ id/action_navigation_library_to_navigation_monster"
app:destination="@id/navigation_monster" />
</fragment>
<fragment
android:id="@ id/navigation_monster"
android:name="com.majinnaibu.monstercards.ui.monster.MonsterDetailFragment"
android:label="Monster"
tools:layout="@layout/fragment_monster">
<argument
android:name="monster_id"
app:argType="string" />
<action
android:id="@ id/action_navigation_monster_to_editMonsterFragment"
app:destination="@id/edit_monster_navigation" />
</fragment>
<navigation
android:id="@ id/edit_monster_navigation"
app:startDestination="@id/editMonsterFragment">
<argument
android:name="monster_id"
app:argType="string" />
<fragment
android:id="@ id/editMonsterFragment"
android:name="com.majinnaibu.monstercards.ui.editmonster.EditMonsterFragment"
android:label="Edit Monster"
tools:layout="@layout/fragment_edit_monster">
<argument
android:name="monster_id"
app:argType="string" />
<action
android:id="@ id/action_editMonsterFragment_to_editBasicInfoFragment"
app:destination="@id/editBasicInfoFragment" />
<!-- other actions here to navigate to fragments in this sub graph -->
</fragment>
<!-- other fragments here -->
</navigation>
<fragment
android:id="@ id/monsterImportFragment"
android:name="com.majinnaibu.monstercards.ui.monster.MonsterImportFragment"
android:label="MonsterImportFragment"
tools:layout="@layout/fragment_monster">
<action
android:id="@ id/action_monsterImportFragment_to_edit_monster_navigation"
app:destination="@id/edit_monster_navigation" />
<deepLink
android:id="@ id/deepLink2"
app:action="ACTION_VIEW"
app:mimeType="application/octet-stream" />
</fragment>
</navigation>
Если я заменю эту глубокую ссылку на это, то приложение выйдет из строя при загрузке с соответствующим действием и типом mimeType.
<deepLink
android:id="@ id/deepLink2"
app:action="ACTION_VIEW"
app:mimeType="application/octet-stream"
app:uri="app://import-monster" />
Ошибка при настройке приложения:uri
06-30 13:41:52.004 19299 19299 E AndroidRuntime: Process: com.majinnaibu.monstercards, PID: 19299
06-30 13:41:52.004 19299 19299 E AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.majinnaibu.monstercards/com.majinnaibu.monstercards.MainActivity}: android.view.InflateException: Binary XML file line #33 in com.majinnaibu.monstercards:layout/activity_main: Binary XML file line #33 in com.majinnaibu.monstercards:layout/activity_main: Error inflating class androidx.fragment.app.FragmentContainerView
06-30 13:41:52.004 19299 19299 E AndroidRuntime: Caused by: android.view.InflateException: Binary XML file line #33 in com.majinnaibu.monstercards:layout/activity_main: Binary XML file line #33 in com.majinnaibu.monstercards:layout/activity_main: Error inflating class androidx.fragment.app.FragmentContainerView
06-30 13:41:52.004 19299 19299 E AndroidRuntime: Caused by: android.view.InflateException: Binary XML file line #33 in com.majinnaibu.monstercards:layout/activity_main: Error inflating class androidx.fragment.app.FragmentContainerView
06-30 13:41:52.005 19299 19299 E AndroidRuntime: at com.majinnaibu.monstercards.MainActivity.onCreate(MainActivity.java:34)
06-30 13:41:52.009 1367 2360 W ActivityManager: crash : com.majinnaibu.monstercards,0
06-30 13:41:52.010 1367 2360 W ActivityTaskManager: Force finishing activity com.majinnaibu.monstercards/.MainActivity
06-30 13:41:52.012 1367 2360 W ActivityTaskManager: Force finishing activity com.majinnaibu.monstercards/.MainActivity
Комментарии:
1. Почему
ImportMonsterActivity
, в первую очередь, это отдельное занятие? Навигация поддерживает глубокие ссылки для действий/типов, начиная с навигации 2.3.0 .2. Я попытался добавить новый фрагмент в навигационный график для импорта, и я создал глубокие ссылки, но я не знаю, что установить для приложения:uri. Я хочу, чтобы пользователь мог открывать любой текст/обычный или приложение/октет-поток для импорта. Вот почему я занялся самостоятельной деятельностью.
3. Навигация не требует a
app:uri
, если вы добавилиapp:action
/app:mimeType
. Что заставило тебя подумать, что тебе это нужноapp:uri
?4. Я получаю сообщение об ошибке сборки, в котором говорится, что мне нужно включить его.
Execution failed for task ':app:extractDeepLinksDebug'. > Navigation XML document <deepLink> element must contain a app:uri attribute.
Ответ №1:
Это обходной путь, потому что я не смог найти ответ. Я надеюсь, что со временем смогу найти лучшее решение. Я не собираюсь отмечать это как ответ, потому что это обходной путь и не отвечает на вопрос.
Я создал фрагмент для замены отдельного действия, но мне не удалось получить глубокую ссылку на него, чтобы он работал с теми же намерениями action= android.intent.action.VIEW
и mimeType= text/plain
или application/octet-stream
вместо этого я обрабатываю намерения в onNewIntent(Intent intent)
методе MainActivity, и если я распознаю намерение импорта, я выполняю навигацию к фрагменту импорта там. Это позволяет мне обычно использовать NavigationController для навигации внутри фрагмента импорта.
Я все еще думаю, что это должно сработать, и я просто не понял, как это сделать, но для тех, кто хочет сделать что-то подобное, мой единственный совет-пересмотреть свое решение, чтобы оно не нуждалось в этом.
В своем манифесте я переместил фильтры намерений в свою основную активность и установил режим запуска для одной задачи. AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.majinnaibu.monstercards">
<application
android:name=".MonsterCardsApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.EDIT" />
<action android:name="android.intent.action.PICK" />
<action android:name="android.intent.action.INSERT" />
<action android:name="android.intent.action.INSERT_OR_EDIT" />
<category android:name="android.intent.category.ALTERNATIVE" />
<category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
<category android:name="android.intent.category.DEFAULT" />
<data
android:mimeType="text/plain"
android:scheme="content" />
<data
android:mimeType="application/octet-stream"
android:scheme="content" />
<data
android:mimeType="text/plain"
android:scheme="file" />
</intent-filter>
<nav-graph android:value="@navigation/mobile_navigation" />
</activity>
</application>
</manifest>
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
String json = readMonsterJSONFromIntent(intent);
if (!StringHelper.isNullOrEmpty(json)) {
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
NavController navController = navHostFragment.getNavController();
NavDirections action = MobileNavigationDirections.actionGlobalMonsterImportFragment(json);
navController.navigate(action);
}
}
private String readMonsterJSONFromIntent(Intent intent) {
String action = intent.getAction();
Bundle extras = intent.getExtras();
String type = intent.getType();
String json;
Uri uri = null;
if ("android.intent.action.SEND".equals(action) amp;amp; "text/plain".equals(type)) {
uri = extras.getParcelable("android.intent.extra.STREAM");
} else if ("android.intent.action.VIEW".equals(action) amp;amp; ("text/plain".equals(type) || "application/octet-stream".equals(type))) {
uri = intent.getData();
} else {
Logger.logError(String.format("unexpected launch configuration action: %s, type: %s, uri: %s", action, type, uri));
}
if (uri == null) {
return null;
}
json = readContentsOfUri(uri);
if (StringHelper.isNullOrEmpty(json)) {
return null;
}
return json;
}
private String readContentsOfUri(Uri uri) {
StringBuilder builder = new StringBuilder();
try (InputStream inputStream =
getContentResolver().openInputStream(uri);
BufferedReader reader = new BufferedReader(
new InputStreamReader(Objects.requireNonNull(inputStream)))) {
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
}
} catch (IOException e) {
Logger.logError("error reading file", e);
return null;
}
return builder.toString();
}
В своей мобильной навигации я создал глобальное действие для своего фрагмента импорта и действие от фрагмента импорта к следующему фрагменту, который я хотел в стеке. Я удалил несвязанные фрагменты моего навигационного xml здесь.
mobile_navigation.xml
<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/mobile_navigation"
app:startDestination="@ id/navigation_dashboard">
<fragment
android:id="@ id/navigation_library"
android:name="com.majinnaibu.monstercards.ui.library.LibraryFragment"
android:label="@string/title_library"
tools:layout="@layout/fragment_library">
<action
android:id="@ id/action_navigation_library_to_navigation_monster"
app:destination="@id/navigation_monster" />
</fragment>
<fragment
android:id="@ id/navigation_monster"
android:name="com.majinnaibu.monstercards.ui.monster.MonsterDetailFragment"
android:label="Monster"
tools:layout="@layout/fragment_monster">
<argument
android:name="monster_id"
app:argType="string" />
<action
android:id="@ id/action_navigation_monster_to_editMonsterFragment"
app:destination="@id/edit_monster_navigation" />
</fragment>
<navigation
android:id="@ id/edit_monster_navigation"
app:startDestination="@id/editMonsterFragment">
<argument
android:name="monster_id"
app:argType="string" />
<fragment
android:id="@ id/editMonsterFragment"
android:name="com.majinnaibu.monstercards.ui.editmonster.EditMonsterFragment"
android:label="Edit Monster"
tools:layout="@layout/fragment_edit_monster">
<argument
android:name="monster_id"
app:argType="string" />
</fragment>
</navigation>
<fragment
android:id="@ id/monsterImportFragment"
android:name="com.majinnaibu.monstercards.ui.monster.MonsterImportFragment"
android:label="MonsterImportFragment"
tools:layout="@layout/fragment_monster">
<argument
android:name="json"
app:argType="string" />
<action
android:id="@ id/action_monsterImportFragment_to_navigation_library"
app:destination="@id/navigation_library"
app:popUpTo="@id/monsterImportFragment"
app:popUpToInclusive="true" />
</fragment>
<action
android:id="@ id/action_global_monsterImportFragment"
app:destination="@id/monsterImportFragment" />
</navigation>
В моем фрагменте импорта я использую этот метод для настройки нужного мне заднего стека и перехода к моему фрагменту редактирования. Я не знаю другого способа удалить текущий фрагмент из стека, не зная, каким будет предыдущий фрагмент, и настроить полный стек, который я хочу, без навигации по всем фрагментам, чтобы поместить их в стек.
MonsterImportFragment.java
private void navigateToEditMonster(UUID monsterId) {
NavController navController = Navigation.findNavController(requireView());
NavDirections action;
action = MonsterImportFragmentDirections.actionMonsterImportFragmentToNavigationLibrary();
navController.navigate(action);
action = LibraryFragmentDirections.actionNavigationLibraryToNavigationMonster(monsterId.toString());
navController.navigate(action);
action = MonsterDetailFragmentDirections.actionNavigationMonsterToEditMonsterFragment(monsterId.toString());
navController.navigate(action);
}
Редактировать: Я думаю, что, к сожалению, просто нет способа сделать это. Я все еще думаю, что эта функция является полностью обоснованным вопросом. Я думаю, что это возможно, если вы создадите глубокую ссылку в основной деятельности для того, на что вы хотите перейти, и перейдете к ней вообще без использования навигационного контроллера.