Using global actions with multiple graphs in jetpack navigation - android-jetpack-navigation

I created an application with a bottom navigation bar and added four different nav graphs using google's advanced navigation example
After that I added a fifth graph called settings that had the settings fragment along with a global action
I added an include to that graph on each of the first four graphs
when I do something like findNavController(R.id.container).navigate(SettingsDirections.showSettings()) the app crashes because it cannot find the destination or the action
but when I copy the fragment and the global action inside each of those graphs and call the above (with that graph's directions) it works
am I missing something? doesn't include actually copy everything from the other graph the original?

It seems that the include tag does not actually include all of the nav graph including the global actions
so in case someone wants to do something similar here is how I did it
first I updated the settings navigation to include a dummy action to show settings like so:
<?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/settings"
app:startDestination="#+id/settings_dest">
<include app:graph="#navigation/questions" />
<!--this is just so safeargs will create a SettingsNavigation.showSettings it is never actually used-->
<action
android:id="#+id/show_settings"
app:destination="#id/settings_dest" />
<fragment
android:id="#+id/settings_dest"
android:name="com.example.app.ui.fragments.SettingsFragment"
android:label="#string/settings"
tools:layout="#layout/fragment_settings" >
<argument
app:argType="com.example.app.ui.model.SettingsProfile"
android:name="profile"
app:nullable="false"/>
</fragment>
</navigation>
and then on every nav graph that I wanted to use the show_settings action I would do by including this:
<include app:graph="#navigation/settings" />
<action
android:id="#+id/show_settings"
app:destination="#id/settings"
app:enterAnim="#anim/fade_in"
app:exitAnim="#anim/fade_out"
app:popEnterAnim="#anim/fade_in"
app:popExitAnim="#anim/fade_out" />
so now when I want to use directions to go to settings I do it like this
findNavController().navigate(SettingsDirections.showSettings(profile))
this will use the directions created in my settings.xml to execute the action in my current nav controller
it is important by the way the id of the action in the included file and the id of the action of the file including it to be the same

Let me show you with an example
<navigation ...
app:startDestination:"#id/nav_graph_one">
<include app:graph:"#navigation/nav_graph_one"/>
<include app:graph:"#navigation/nav_graph_two"/>
<fragment
android:id="#+id/aCommonFragment"
.../>
<action
android:id="#+id/action_grobal_aCommonFragment
app:destination="#+id/aCommonFragment"/>
</navigation>
Any where from your app you can now use
findNavController().navigate(NavGraphDirections.actionGlobalACommonFragment)

By copying nodes and actions from global actions graph to main graph in runtime it is possible to use global actions in all subgraphs and stay them in separate file.
main graph:
<navigation android:id="#+id/main_graph">
<include app:graph="#navigation/subgraph1"/>
<include app:graph="#navigation/subgraph2"/>
<navigation/>
global actions graph:
<navigation android:id="#+id/customer_dialogs">
<action android:id="#+id/actionToShareCustomer"
android:destionation="#id/shareCustomerDialog"/>
<dialog android:id="#+id/shareCustomerDialog">
<argument android:id="#+id/customerId" app:argType="long"/>
</dialog>
<navigation/>
programmatically set main_graph to navController:
override fun onCreate(savedInstanceState: Bundle?) {
with(navHostFragment.findNavController()) {
val mainGraph = navInflater.inflate(R.navigation.main_graph)
val actionsGraph = navInflater.inflate(R.navigation.customer_dialogs)
copyGraph(actionsGraph, mainGraph)
setGraph(mainGraph, null)
}
}
fun copyGraph(from: NavGraph, to: NavGraph) {
to.addAll(from)
val actions = NavDestination::class.java.getDeclaredField("actions").let {
it.isAccessible = true
it.get(from)
} as? SparseArrayCompat<NavAction>
actions?.forEach { actionId, action -> to.putAction(actionId, action) }
}
usage of action from fragment of subgraph1:
private fun shareCustomer(customerId: Long) {
findNavController().navigate(CustomerDialogsDirections.actionToShareCustomer(customerId))
}

I created an extension function when setting the default animation
fun View.navigate(id: Int, navOptions: NavOptions? = null){
var updatedNavOptions = navOptions
if(updatedNavOptions == null){
updatedNavOptions = navOptions {
anim {
enter = R.anim.slide_in_right
exit = R.anim.slide_out_left
popEnter = R.anim.slide_in_left
popExit = R.anim.slide_out_right
}
}
}
this.findNavController().navigate(id, null, updatedNavOptions)
}
In my fragment just do so:
my_view.click { view?.navigate(R.id.how_referral_program_works) }

Related

Is there possibility for FreeMarker to create a directory or package on Android Studio templating?

I'm trying to create a template that has a nested package.
Here's my templating code.
// root/global.xml.ftl
<?xml version="1.0"?>
<globals>
<global id="simpleLayoutName" value="${layoutName}"/>
<global id="excludeMenu" type="boolean" value="true"/>
<global id="resOut" value="${resDir}"/>
<global id="srcOut" value="${srcDir}/${slashedPackageName(packageName)}"/>
<#include "../common/common_globals.xml.ftl" />
</globals>
// root/recipe.xml.ftl
<?xml version="1.0"?>
<recipe>
<#if generateLayout>
<#include "../common/recipe_simple.xml.ftl" />
<open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml"/>
</#if>
<instantiate from="root/src/app_package/SimpleFragment.kt.ftl"
to="${escapeXmlAttribute(srcOut)}/${activityClass}Fragment.kt"/>
</recipe>
// root/template.xml
<?xml version="1.0"?>
<template
format="5"
revision="1"
name="MVVM Activity"
minApi="9"
minBuildApi="14"
description="Creates a new empty activity that uses MVVM Pattern">
<category value="Activity" />
<formfactor value="Mobile" />
<parameter
id="generateLayout"
name="Generate Layout File"
type="boolean"
default="true"
help="If true, a layout file will be generated" />
<parameter
id="layoutName"
name="Layout Name"
type="string"
constraints="layout|unique|nonempty"
suggest="${activityToLayout(activityClass)}"
default="activity_main"
visibility="generateLayout"
help="The name of the layout to create for the activity" />
<parameter
id="packageName"
name="Package name"
type="string"
constraints="package"
default="com.mycompany.myapp" />
<!-- 128x128 thumbnails relative to template.xml -->
<thumbs>
<!-- default thumbnail is required -->
<thumb>template_MVVM_activity.png</thumb>
</thumbs>
<globals file="globals.xml.ftl" />
<execute file="recipe.xml.ftl" />
</template>
// root/src/app_package/SimpleFragment.kt.ftl
package ${packageName}
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
class ${fragmentClass}Fragment : Fragment() {
// TODO: Implement by lazy ViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// TODO: Init or setup view, viewmodel related, etc anything to setup here
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// TODO: Init or setup view, viewmodel related, etc anything to setup here
}
}
But, I didn't find any function to create a directory here: http://freemarker.apache.org/docs
Looks like I'm missing something here, is there anyone ever doing this to achieve creating a package inside output directory.
I quite catch that recipe.xml.ftl able to instantiate new file, but I still didn't try to create a directory instead, is it possible? What should add for from attribute value?
According to this article. By default, you can do this without any extra effort. Just point the new package on recipe.xml.ftl e.g:
<instantiate from="root/src/main/java/model/Simple.kt.ftl"
to="${escapeXmlAttribute(srcOut)}/model/${modelName}.kt"/>
to element will creating the new model package inside your destination directdory.

How to share audio files in Android Studio (Kotlin)?

How can I share an audio file in Android Studio? I have tried the following so far, but it doesn't work.
button.setOnLongClickListener(OnLongClickListener {
val sendIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
var path = "absolute/path"
var uri = Uri.parse(path)
putExtra(Intent.EXTRA_STREAM, uri)
type = "audio/mp3"
}
startActivity(sendIntent)
true
})
Your code seems fine, but I'd suggest you to try this one:
String sharePath = Environment.getExternalStorageDirectory().getPath()
+ "/Soundboard/Ringtones/custom_ringtone.ogg"; //This is the path of your audio file
Uri uri = Uri.parse(sharePath); //Identifier of the audio file (Uniform Resource Identifier)
Intent share = new Intent(Intent.ACTION_SEND); //Create a new action_send intent
share.setType("audio/*"); //What kind of file the intent gets
share.putExtra(Intent.EXTRA_STREAM, uri); //Pass the audio file to the intent
startActivity(Intent.createChooser(share, "Share Sound File")); //Start the intent
If that doesn't work either, make sure that you granted the right permissions in the Manifest.xml file (WRITE_EXTERNAL_STORAGE):
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
I suggest you look into how to use a file provider. You'll find that you won't be able to provide the uri of the file directly as you're only allowed to expose its content uri. Have a look at the Generating the Content URI for a File section
This is worked for me
Call this method onClick Button:
fun shareAudioFile(audioFile: File) {
val uri = FileProvider.getUriForFile(applicationContext,
"com.app.package.fileprovider",
File(audioFile)
val shareIntent: Intent = ShareCompat.IntentBuilder.from(this#MainActivity)
.setType("audio/mp3")
.setStream(uri)
.intent
startActivity(Intent.createChooser(shareIntent, "Share Sound File"))
}
I replaced Uri.parse with FileProvider.getUriForFile.
Replace com.app.package with your package.
Add on Android Manifest:
<application ...>
...
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths"/>
</provider>
<application/>
applicationId is your package
Create file_paths.xml file into xml folder
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="external_files"
path="." />
</paths>
Share Any File as below ( Kotlin ) :
first create a folder named xml in the res folder and create a new XML Resource File named provider_paths.xml and put the below code inside it :
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<files-path
name="files"
path="."/>
<external-path
name="external_files"
path="."/>
</paths>
now go to the manifests folder and open the AndroidManifest.xml and then put the below code inside the <application> tag :
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths" /> // provider_paths.xml file path in this example
</provider>
now you put the below code in the setOnLongClickListener :
button.setOnLongClickListener {
try {
val file = File("pathOfFile")
if(file.exists()) {
val uri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".provider", file)
val intent = Intent(Intent.ACTION_SEND)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.setType("*/*")
intent.putExtra(Intent.EXTRA_STREAM, uri)
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent)
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
toast("Error")
}
}

Android Plugin for unity not opening permission dialogue in unity?

I just develop android plugin for unity and i am successfully able to exchange data between unity and android plugin.
in my android plugin my code is
String SCOPE = "https://www.googleapis.com/auth/youtube";
am.getAuthToken(myAccount_[0], "oauth2:" + SCOPE, null, this,
new OnTokenAcquired(), null);
It should prompt me to allow my app to access youtube.
Its working fine for my android application but not be able to ask on unity.
here is my code of android:
public void YoutubeSubscriber() {
AccountManager am = AccountManager.get(context);
Account[] myAccount_ = AccountManager.get(context).getAccountsByType("com.google");
Log.d("==============>all", String.valueOf(myAccount_));
Log.d("==============>single", String.valueOf(myAccount_[0]));
String SCOPE = "https://www.googleapis.com/auth/youtube";
am.getAuthToken(myAccount_[0], "oauth2:" + SCOPE, null, null,
new OnTokenAcquired(), null);
Toast.makeText(this.context, myAccount_[0].toString(), Toast.LENGTH_SHORT).show();
}
and using this in unity is like that.
using (AndroidJavaClass activityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
info.text = "activity";
activityContext = activityClass.GetStatic<AndroidJavaObject>("currentActivity");
}
using (AndroidJavaClass pluginClass = new AndroidJavaClass("com.thegamecontriver.androidlib.ToastExample"))
{
if (pluginClass != null)
{
toastExample = pluginClass.CallStatic<AndroidJavaObject>("instance");
toastExample.Call("setContext", activityContext);
info.text = "After ";
toastExample.Call("YoutubeSubscriber");
/*
activityContext.Call("runOnUiThread", new AndroidJavaRunnable(() => {
toastExample.Call("YoutubeSubscriber");
}));
*/
}
}
Note: i am able to see toast but permission is not prompting. please help
You are likely missing the permission required (USE_CREDENTIALS) to use AccountManager.
From getAuthToken SDK reference:
NOTE: If targeting your app to work on API level 22 and before,
USE_CREDENTIALS permission is needed for those platforms. See docs for
this function in API level 22.
1.Go to <UnityInstallationDirecory>\Editor\Data\PlaybackEngines\AndroidPlayer\Apk, Copy the AndroidManifest.xml file to your <ProjectName>Assets\Plugins\Android
2.Now open the copied Manifest file from <ProjectName>Assets\Plugins\Android and add <uses-permission android:name="android.permission.USE_CREDENTIALS"/> to it. Save, Build and Run. If this is a permission problem, that should now be solved.
What your AndroidManifest.xml should look like(Unity 5.4):
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unity3d.player"
android:versionCode="1"
android:versionName="1.0">
<uses-permission android:name="android.permission.USE_CREDENTIALS"/>
<application
android:theme="#style/UnityThemeSelector"
android:icon="#drawable/app_icon"
android:label="#string/app_name"
android:debuggable="true">
<activity android:name="com.unity3d.player.UnityPlayerActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
</activity>
</application>
</manifest>

Android ViewFlipper with translation animation - not working properly

I want to display banner(images moving in x direction) in my application. For that i am using ViewFlipper with Translation animation. Please find my below code..
my layout: banner.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ViewFlipper
android:id="#+id/banner_image"
android:layout_width="match_parent"
android:layout_height="258dp" />
</RelativeLayout>
In animation: in_from_right.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false" >
<translate
android:duration="3000"
android:fromXDelta="100%"
android:fromYDelta="0%"
android:toXDelta="0%"
android:toYDelta="0%" />
</set>
Out Animation: out_to_left.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate android:fromXDelta="0%" android:toXDelta="-100%"
android:fromYDelta="0%" android:toYDelta="0%"
android:duration="3000"/>
</set>
My Java Code
public class BannerView extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.banner);
ViewFlipper myViewFlipper = (ViewFlipper) findViewById(R.id.banner_image);
//Setting first image in viewFlipper
ImageView imageView1 = new ImageView(HomeView.this);
imageView1.setImageDrawable(getResources().getDrawable(R.drawable.img1));
myViewFlipper.addView(imageView1);
//Setting second image in viewFlipper
ImageView imageView2 = new ImageView(HomeView.this);
imageView2.setImageDrawable(getResources().getDrawable(R.drawable.img2));
myViewFlipper.addView(imageView2);
myViewFlipper.setAutoStart(true);
//Setting in and out animation
myViewFlipper.setInAnimation(HomeView.this,R.anim.in_from_right);
myViewFlipper.setOutAnimation(HomeView.this,R.anim.out_to_left);
//Starting the view filpper to rotate
myViewFlipper.startFlipping();
}
}
Here my problem is,
When i set the duration for in and out animation as "5000", ViewFilpper's behavior is changing like the below,
Image start to move slowly and ending quickly.
I dont know where i am missing. I want to slow down the speed. Please help me resolve the issue.
Update: I whipped up some sample code and there is a big stutter halfway through as if the 2nd view is forcibly moved to catch up to the 1st view. After much trial and error I found that setting android:flipInterval="5000" in the viewflipper xml properties fixed it. I can only assume that the default flipInterval is shorter than 5000 and hence the issue.
<ViewFlipper
android:id="#+id/viewflipper"
android:layout_width="match_parent"
android:layout_height="258dp"
android:flipInterval="5000" >
If you still find the animation not smooth enough:
Put this in your xml for the translate animation:
android:interpolator="#android:anim/linear_interpolator"
And then
android:startOffset="1000"
to slide_out_to_left so it waits a bit before continuing.
Thanks for your reply. I found the solution for my problem. Please find my answer below.
I have changed my interpolator in xml as below:
android:interpolator="#android:anim/decelerate_interpolator"
And then i used android:flipInterval="5000" in my viewflipper solved my problem.

How to 'force' Magento to load a different layout

Background: I need to be able to load upsell / crosssell products in a lightbox complete with add-to-cart functionality.
My idea for achieving this was to 'force' Magento to load products in a different layout. I thought of using an observer on the controller_action_layout_generate_xml_before event (code below).
Unfortunately what I have is not working. Any pointers (or completely different / better ideas) are much appreciated.
<?php
class My_ForceLayout_Model_Observer
{
public function changeLayoutEvent($observer)
{
$action = $observer->getEvent()->getAction();
$layout = $observer->getEvent()->getLayout();
if($action->getRequest()->getControllerName() == 'product'
&& $action->getRequest()->getActionName() == 'view')
{
$update = $layout->getUpdate();
$update->load('popup'); // for testing only
$layout->generateXml();
}
}
}
I managed to get this to work exactly as I first intended. Thanks to #Jonathan Day for making me realize the reason it was not working was trivial.
Config.xml:
<config>
....
<frontend>
<events>
<controller_action_layout_generate_blocks_before>
<observers>
<forcelayout>
<type>singleton</type>
<class>forcelayout/observer</class>
<method>changeLayoutEvent</method>
</forcelayout>
</observers>
</controller_action_layout_generate_blocks_before>
</events>
</frontend>
....
</config>
Observer.php:
class Unleaded_ForceLayout_Model_Observer
{
public function changeLayoutEvent($observer)
{
$action = $observer->getEvent()->getAction();
$layout = $observer->getEvent()->getLayout();
if($action->getRequest()->getControllerName() == 'product'
&& $action->getRequest()->getActionName() == 'view')
{
$template = $action->getRequest()->template;
if (isset($template) && $template != '')
{
$update = $layout->getUpdate();
$update->load($template);
$layout->generateXml();
}
}
}
}
Local.xml:
<popup translate="label">
<label>Catalog Product View Lightbox</label>
<remove name="right"/>
<remove name="left"/>
<reference name="root">
<action method="setTemplate">
<template>page/popup.phtml</template>
</action>
</reference>
<reference name="content">
<remove name="product.info.upsell"/>
</reference>
</popup>
Product url in .phtml file:
echo $this->getProductUrl($_item) . '?template=popup';
Why don't you want to use just regular layout udates?
<catalog_product_view translate="label">
<label>Catalog Product View (Any)</label>
<!-- Mage_Catalog -->
<remove name="right"/>
<remove name="left"/>
<reference name="content">
<block type="new_catalog/product_view"
name="new.product.info"
template="new/catalog/product/view_popup.phtml">
...
</block>
</reference>
</catalog_product_view>
If you want to change the design of your product page depends on some conditions, you could use layout handler functionality. It means that you have to check your parameters in controller and add handler for layout updates, then you could use it in layout file as any other handler. For example:
if ($this->check_parameters()) {
$update->addHandle('new_magic_handler');
$this->loadLayoutUpdates();
}
And in layout:
<new_magic_handler translate="label">
<label>New Magic</label>
...
</new_magic_handler>
Check for details Mage_Catalog_ProductController::_initProductLayout()

Resources