talkback not focusing by default on any view on start of inner fragment - kotlin

I am using accessibility talkback functionality and I am facing one problem I have one bottom navigation in the parent activity and from the setting tab I am opening another fragment(inner fragment) using .add but the inner fragment view not getting focus by default
I also tried with . replace but it's not focusing by default on fragment creation.
open fragment code
val details = DetailsFragment.newInstance();
getSupportFragmentManager().setupForAccessibility()
getSupportFragmentManager().beginTransaction().add(android.R.id.content, details).commit()
and I used this extension function to not get focus on the previous fragment from this source
fun FragmentManager.setupForAccessibility() {
addOnBackStackChangedListener {
val lastFragmentWithView = fragments.lastOrNull { it.view != null }
for (fragment in fragments) {
if (fragment == lastFragmentWithView) {
fragment.view?.importantForAccessibility =
View.IMPORTANT_FOR_ACCESSIBILITY_YES
} else {
fragment.view?.importantForAccessibility =
View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
}
}
}
}
in normal I show that at the start of the first fragment it's focusing top first Textview and speaking automatic but in the inner fragment it's not focusing by default so what should I do to get focus by default on the first view by default
I already try
android:focusable="true"
android:focusableInTouchMode="true"
and request focus but it's not working
Please suggest me any help would be highly appriciated

I've had the best luck using
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
on the view you want to focus, or maybe
Handler().postDelayed({
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
}, someDelayMillis)
to let the system do whatever it does, and then jump in after a moment and force a focus change.
But this might be considered bad accessibility, if it's interfering with the usual navigation, and that might be why it's so hard to get focus working consistently! It might be better to just announce the new fragment (with something like view.announceForAccessibility("new fragment")) and let the user start navigating from the top. It depends on your app, it's your call
Also you probably want to use replace for your fragment instead of add, if you add a fragment on top of an old one, TalkBack can get stuck looking at the "invisible" views on the old fragment

this is my improved code that extends from #cactustictacs
//target to specific a view
binding.getRoot().postDelayed(new Runnable() {
#Override
public void run() {
binding.textView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
}
}, 300);
full: https://github.com/dotrinh-PM/AndroidTalkback

Related

How Should I implement onbackpressed() in fragment kotlin [duplicate]

I am using The new Navigation Architecture Component in android and I am stuck in clearing the navigation stack after moving to a new fragment.
Example:
I am in the loginFragment and I want this fragment to be cleared from the stack when I navigate to the home fragment so that the user will not be returned back to the loginFragment when he presses the back button.
I am using a simple NavHostFragment.findNavController(Fragment).navigate(R.id.homeFragment) to navigate.
Current Code :
mAuth.signInWithCredential(credential)
.addOnCompleteListener(getActivity(), new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
NavHostFragment.findNavController(LoginFragment.this).navigate(R.id.homeFragment);
} else {
Log.w(TAG, "signInWithCredential:failure", task.getException());
}
}
});
I tried using the NavOptions in the navigate(), but the back button is still sending me back to the loginFragment
NavOptions.Builder navBuilder = new NavOptions.Builder();
NavOptions navOptions = navBuilder.setPopUpTo(R.id.homeFragment, false).build();
NavHostFragment.findNavController(LoginFragment.this).navigate(R.id.homeFragment, null, navOptions);
First, add attributes app:popUpTo='your_nav_graph_id' and app:popUpToInclusive="true" to the action tag.
<fragment
android:id="#+id/signInFragment"
android:name="com.glee.incog2.android.fragment.SignInFragment"
android:label="fragment_sign_in"
tools:layout="#layout/fragment_sign_in" >
<action
android:id="#+id/action_signInFragment_to_usersFragment"
app:destination="#id/usersFragment"
app:launchSingleTop="true"
app:popUpTo="#+id/main_nav_graph"
app:popUpToInclusive="true" />
</fragment>
Second, navigate to the destination, using above action as parameter.
findNavController(fragment).navigate(
SignInFragmentDirections.actionSignInFragmentToUserNameFragment())
See the docs for more information.
NOTE: If you navigate using method navigate(#IdRes int resId), you won't get the desired result. Hence, I used method navigate(#NonNull NavDirections directions).
I think your question specifically pertains on how to use the Pop Behavior / Pop To / app:popUpTo (in xml)
In documentation,
Pop up to a given destination before navigating. This pops all non-matching destinations from the back stack until this destination is found.
Example (Simple Job hunting app)
my start_screen_nav graph is like this:
startScreenFragment (start) -> loginFragment -> EmployerMainFragment
-> loginFragment -> JobSeekerMainFragment
if I want to navigate to EmployerMainFragment and pop all including startScreenFragment then the code will be:
<action
android:id="#+id/action_loginFragment_to_employerMainFragment"
app:destination="#id/employerMainFragment"
app:popUpTo="#+id/startScreenFragment"
app:popUpToInclusive="true" />
if I want to navigate to EmployerMainFragment and pop all excluding startScreenFragment then the code will be:
<action
android:id="#+id/action_loginFragment_to_employerMainFragment"
app:destination="#id/employerMainFragment"
app:popUpTo="#+id/startScreenFragment"/>
if I want to navigate to EmployerMainFragment and pop loginFragment but not startScreenFragment then the code will be:
<action
android:id="#+id/action_loginFragment_to_employerMainFragment"
app:destination="#id/employerMainFragment"
app:popUpTo="#+id/loginFragment"
app:popUpToInclusive="true"/>
OR
<action
android:id="#+id/action_loginFragment_to_employerMainFragment"
app:destination="#id/employerMainFragment"
app:popUpTo="#+id/startScreenFragment"/>
In my case i needed to remove everything in the back Stack before i open a new fragment so i used this code
navController.popBackStack(R.id.fragment_apps, true);
navController.navigate(R.id.fragment_company);
the first line removes the back Stack till it reaches the fragment specified in my case it's the home fragment so it's removes all the back stack completely , and when the user clicks back in the fragment_company he closes the app.
Going to add another answer here as none of the above worked for me ... we have multiple nav graphs.
findNavController().navigate(R.id.dashboard_graph,null,NavOptions.Builder().setPopUpTo(findNavController().graph.startDestination, true).build())
This was the only way that I could successfully clear the full back stack. Google really need to make this simpler.
NOTE: Clear task is deprecated, official description is
This method is deprecated. Use setPopUpTo(int, boolean) with the id of the NavController's graph and set inclusive to true.
Old Answer
If you don't wanna go through all that fuzz in code, you can simply check Clear Task in Launch Options in properties of the action.
Edit: As of Android Studio 3.2 Beta 5, Clear Task is no longer visible in Launch Options window, but you can still use it in navigation's XML code, in action tag, by adding
app:clearTask="true"
NavController navController
=Navigation.findNavController(requireActivity(),
R.id.nav_host_fragment);// initialize navcontroller
if (navController.getCurrentDestination().getId() ==
R.id.my_current_frag) //for avoid crash
{
NavDirections action =
DailyInfoSelectorFragmentDirections.actionGoToDestionationFragment();
//for clear current fragment from stack
NavOptions options = new
NavOptions.Builder().setPopUpTo(R.id.my_current_frag, true).build();
navController.navigate(action, options);
}
I finally figure it out thanks to How to disable UP in Navigation for some fragment with the new Navigation Architecture Component?
I had to specify .setClearTask(true) as a NavOption.
mAuth.signInWithCredential(credential)
.addOnCompleteListener(getActivity(), new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
Log.d(TAG, "signInWithCredential:success");
NavOptions.Builder navBuilder = new NavOptions.Builder();
NavOptions navOptions = navBuilder.setClearTask(true).build();
NavHostFragment.findNavController(LoginFragment.this).navigate(R.id.homeFragment,null,navOptions);
} else {
Log.w(TAG, "signInWithCredential:failure", task.getException());
}
}
});
use this code
navController.navigateUp();
then call new Fragment
android version 4.1.2
Here is how I am getting it done.
//here the R.id refer to the fragment one wants to pop back once pressed back from the newly navigated fragment
val navOption = NavOptions.Builder().setPopUpTo(R.id.startScorecardFragment, false).build()
//now how to navigate to new fragment
Navigation.findNavController(this, R.id.my_nav_host_fragment)
.navigate(R.id.instoredBestPractice, null, navOption)
For
// Navigation library
def nav_version = "2.3.5"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
This solution work for me
findNavController().popBackStack(R.id.<Current Fragment Id In NavGraph>, true)
findNavController().navigate(R.id.< Your Destination Fragment in NavGraph>)
In my case where I used Navigation component with NavigationView (menu drawer):
1.
mNavController.popBackStack(R.id.ID_OF_FRAGMENT_ROOT_TO_POP, true)
mNavController.navigate(
R.id.DESTINATION_ID,
null,
NavOptions.Builder()
.setPopUpTo(R.id.POP_TO_DESTINATION_ID, true)
.build()
)
I wanted to clear the stack after clicking on logout on side menu drawer!
Hope that helped someone!
You can override the back pressed of the base activity like this :
override fun onBackPressed() {
val navigationController = nav_host_fragment.findNavController()
if (navigationController.currentDestination?.id == R.id.firstFragment) {
finish()
} else if (navigationController.currentDestination?.id == R.id.secondFragment) {
// do nothing
} else {
super.onBackPressed()
}
}
Non of the solutions above works for me.
After spending hours on it, here is my solution:
Note: I have multiple nav_graphs and switching between fragments in different nav_graphs.
Define your action as below in xml:
<action
android:id="#id/your_action_id"
app:destination="#id/the_fragment_id_you_want_to_navigate_to"
app:popUpTo="#id/nav_graph_which_contains_destination_fragment"
app:popUpToInclusive="true" />
Navigate using action above from your Java/Kotlin code:
findNavController(R.id.your_nav_name)?.apply {
navigate(R.id.your_action_id)
backQueue.clear()
}
For Jetpack Compose ❤️
navHostController.navigate(Routes.HOME) {
this.popUpTo(Routes.ONBOARDING) {
this.inclusive = true
}
}
I struggled for a while to prevent the back button from going back to my start fragment, which in my case was an intro message that should only appear once.
The easy solution was to create a global action pointing to the destination that the user should stay on. You have to set app:popUpTo="..." correctly - set it to the destination you want to get popped off. In my case it was my intro message. Also set app:popUpToInclusive="true"
In my case, I am using 2 different activities that have their own respective navigation graphs. My first activity is the host for "nav_graph" and has fragments that deal with authentification and the second is the host for "nav_graph_home". Here you can see the settings I have done for nav_graph.
nav_graph example
Then back in my code for the login fragment, I have this written :
findNavController().navigate(R.id.action_logInFragment_to_nav_graph_home)
After the user logs in and they hit the back button the app will close. Remember to set the pop behavior so it pops till your current navigation graph that contains your login fragment without including it.
Edit:
After this, the up button still appears in the top bar. To avoid this behavior we need to tell the first activity which fragments are considered top level. To do this simply add in the params list of the "setupActionBarWithNavController()" method in addition to the nav host fragment an App bar configuration that contains a set of the home fragment of your first navigation graph and your second. Your code should look something like this:
class MainActivity : AppCompatActivity(R.layout.activity_main) {
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Retrieve NavController from the NavHostFragment
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
navController = navHostFragment.navController
// Set up the action bar for use with the NavController
setupActionBarWithNavController(navController, AppBarConfiguration(setOf(R.id.logInFragment,R.id.homeFragment)))
}
/**
* Handle navigation when the user chooses Up from the action bar.
*/
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp() || super.onSupportNavigateUp()
}
}
This is my first ever contribution, hope this helps.
For androidx.compose version 1.2.0+
I had a few issues with lower versions but 1.2 plus (beta at the time of writing this), works perfectly.
Better syntax for the navGraph in Compose:
navController.navigate(item.name) {
navController.graph.startDestinationRoute?.let { route ->
// use saveState = false to NOT save the state for the popped route
popUpTo(route) { saveState = true }
}
launchSingleTop = true
restoreState = true
}
I am using button to navigate to other fragments so on each button click I am doing this.
val navOptions = NavOptions.Builder().setLaunchSingleTop(true).setPopUpTo(R.id.homeFragment, false).build()
findNavController(R.id.mainNavHostFragment).navigate(R.id.destination, null, navOptions)
You can do as simple as:
getFragmentManager().popBackStack();
If you want to check the count you can do as:
getFragmentManager().getBackStackEntryCount()

How to change the fonts of all items on recyclerview runtime

I wanted to change font family of items on a recycler view every time I click a button.
So I coded like below.
rbAritaBuri = view.findViewById(R.id.rb_aritaBuri)
rbCafe24 = view.findViewById(R.id.rb_cafe24SurroundAir)
rbAritaBuri.setOnClickListener {
rv_work_preview.tv_work_content.typeface = Typeface.createFromAsset(requireActivity().assets, "fonts/arita_buri.otf")
}
rbCafe24.setOnClickListener {
rv_work_preview.tv_work_content.typeface = Typeface.createFromAsset(requireActivity().assets, "fonts/cafe24_surround_air.ttf")
}
But it changes only the font family of the first item of the recycler view.
Is there a way to change fonts of them all together runtime? And please tell me why the code I wrote doesn't work right.
Thank you.
If I were in your position, I would:
Put your font changing calls inside of onBindViewHolder(). If you have to, you could put a bool in there like buttonClicked and link its value to your buttons.
Come up with a good way to force a call to onBindViewHolder(). Sometimes notifyDataSetChanged() is enough. But in some cases, you might have to remove the adapter by setting it to null and then reset the adapter to its original value.
Place that logic from step 2 inside of your buttons' onClick()s.
Edit:
What I mean is, create a var inside the class with the most exterior scope, so outside of oncreate().
var textChoice=""
Now use your buttons to change that var.
rbAritaBuri.setOnClickListener {
textChoice="fonts/arita_buri.otf"
}
Now inside your onBindViewHolder(), make the font switch.
when (fontChoice){
"fonts/arita_buri.otf"->{ rv_work_preview.tv_work_content.typeface = Typeface.createFromAsset(requireActivity().assets, "fonts/arita_buri.otf")}
//and so on and so forth for all of your fonts
Now when you want to show the change, call notifyDatasetChanged(). I think maybe the best place to do that would be inside of your buttons. So maybe you'd actually have:
rbAritaBuri.setOnClickListener {
textChoice="fonts/arita_buri.otf"
<The name of your recyclerview adapter>.notifyDatasetChanged()
}
Here is how I solved it, thanks to D. Kupra:
class SampleWorkAdapter(private val context: Context) :
RecyclerView.Adapter<SampleWorkAdapter.ViewHolder>() {
var selectedFont = EditTextActivity.HAMBAK_SNOW
First, I assigned the default font Hambak_snow to selectedFont, type String.
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
...
fun changeFont(font: String) {
CustomFontHelper.setCustomFont(content, font, itemView.context)
} ...
}
Then I wrote a function to be called on onBindViewHolder to change font-family of textview, using custom typeface. https://stackoverflow.com/a/16648457/15096801 This post helped a lot.
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
...
viewHolder.changeFont(selectedFont)
...
}
Now, replaceFont will be called when the variable selectedFont get changed and adapter.notifyDatasetChanged() is called on an activity, like this:
rbMapoFlowerIsland.setOnClickListener {
sampleWorkAdapter.selectedFont = EditTextActivity.MAPO_FLOWER
sampleWorkAdapter.notifyDataSetChanged()
}

NavigationLink button focusable override issue

I face an issue which stucks for days. I am createing a tvos application which reqiures a custome navigationlink(button), when I move the focus to the navigation item, it should scale a little bit, and also I need to change the parent's view backgound. It is pretty simple, but it seems that the focusabe override the my custome button Style. The test shows that the background image was changed but without any scale effect when the navigationbutton get focused. Any suggestion?
NavigationLink(destination: Text("myview"))
{Text("Test")
}
.buttonStyle(myButtonStyle())
.Focusable(true){(focus) in
//the code to change the background image
//myButtonStyle definition
struct MyButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
return AppButton(configuration: configuration)
}
}
struct AppButton: View {
#Environment(\.isFocused) var focused: Bool
let configuration: ButtonStyle.Configuration
var body: some View {
configuration.label
.scaleEffect(focused ? 1.1 : 1.0)
.focusable(true)
}
}
The line to change the background image is always called when the item get focused as my expected, but the scale effect is gone. If I remove the following line of codes, the scale effect is back:
// .Focusable(true){(focus) in
//the code to change the background image
// }
It looks like that this line of code override my custome style of navigation button, any ideas? Appreciate any help!
Ah, finally I found the tricky, though there is very little document about this. When Focusable was introduced, it should not be in your code to change focus engine, which will cause the navigationlink tap message uncaptured, then your navigationlink for another view will not work.
Use .onChange() function to deal with any focus change event, not use Focusable.

How to switch to another XML layout after click on button?

I would like to have one kotlin file with the logic and I would like to allow users to switch between two different XLM layouts (logic of program is still the same, but layout of buttons shall be changed when clicking on button).
I simply add setContentView function to setOnClickListener for this button in order to load activity_main_second_layout.xml layout.
PS. activity_main_second_layout.xml is almost the same like activity_main.xml, I only changed the position of elements (not the names of elements)
button_switch_to_the_second_design.setOnClickListener {
setContentView(R.layout.activity_main_second_layout);
}
When clicking on the button, voala, the layout really changes to the second one.
BUT the functionality of the program is not working any more, the logic disappear. It seems that I need to resume running of the program somehow to make the code working again without interuption including loss of variables.
There is a lot of ways to do that.
In my opinion you should not try to change layout in runtime - it's possible, but you have to override setContentView and rebind all views and all listeners (or do it in other method, which will be called after changing the layout).
So... Sth like this:
fun sth() {
setContentView(R.layout.activity_main_second_layout)
rebindLayout(R.layout.activity_main_second_layout)
}
fun rebindLayout(#LayoutRes layoutId: Int) {
when (layoutId) {
R.layout.activity_main_first_layout -> { /* rebind views here */ }
R.layout.activity_main_second_layout -> { /* rebind views here */ }
}
}
The other's, better I think is to create independent fragments and change fragment via fragmentManager.
Others approches - ViewAnimator, ViewSwitcher.

Removing the BackStack Entry in MetroStyle Application

How can I implement removing the backStack Entry in Metro style applications?
frame.SetNavigationState("1,0");
will clear the navigation history for you.
I found this answer useful:
How to clear Backstack of the frame and start afresh
Write your own NavigationService and store the navigationstate in the constructor.
string state;
public NavigationService(Frame mainFrame)
{
state = mainFrame.GetNavigationState();
_mainFrame = mainFrame;
_mainFrame.Navigating += _mainFrame_Navigating;
}
Then implement this method on the service and call it when needed:
public void ClearBackstack()
{
_mainFrame.SetNavigationState(state);
}
It doesn't seem to be possible. If you want to clear the back stack entirely (e.g. if you have a "home" button), you can use the code supplied in the LayoutAwarePage.cs file in the grid sample app.
if (this.Frame != null)
{
while (this.Frame.CanGoBack) this.Frame.GoBack();
}
While this doesn't actually clear the stack, it does take you back to the program's start location and there will be no further back-direction entries in the list. If you want to back out of a dead-end page by pressing a button, you could modify this behaviour to step back a number of pages and effectively remove the back entries.