In my kotlin project, I want to refresh fragments from an Actvity. When I finish Activity, it should refresh the fragment. I was thinking this would be done in onResume. So how to do refresh current fragment when I resume an activity? My fragment page contains listviews, hence it should be updated, when i finish activity.
try this
Use Swipe to refresh layout
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/listView"
/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
MainActivity
class MainActivity : AppCompatActivity() {
lateinit var swipeRefreshLayout: SwipeRefreshLayout
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
swipeRefreshLayout = findViewById(R.id.swipeRefreshLayout)
swipeRefreshLayout.setOnRefreshListener {
//TODO operation
}
}
}
Related
I have an application composed of one activity and several fragments, as recommanded by Google. Other details here. I would like to keep a menu still and to switch my fragments in the container in the center.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:someProperties="propertiesValues">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:someProperties="propertiesValues" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView
android:id="#+id/fragment_container_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/navigation_map"
/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/fab"
android:someProperties="propertiesValues" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
println("Activity creation")
val binding = ActivityMainBinding.inflate(layoutInflater)
println("Activity creation part 2")
setContentView(binding.root)
setSupportActionBar(binding.toolbar)
User.initSharedPref(this)
}
Fragment
private lateinit var mylist: MutableList<String>>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
println("Fragment creation")
mylist = User.loadScenarioList()
}
User
object User
{
private lateinit var sharedPref : SharedPreferences
fun initSharedPref(context: Context){
sharedPref = context.getSharedPreferences("JeuDePisteKtPreferenceFileKey",Context.MODE_PRIVATE)
}
fun loadList(): MutableList<String>> {
val json = sharedPref.getString(KEY_LIST, "") ?: ""
if (json == "") return mutableListOf()
return Json.decodeFromString(json)
}
}
Problem encountered
When i start the activity, it initialize a variable sharedPref as shown in code.
But when in fragment onCreate i use this variable (mylist = User.loadScenarioList()), the binding line in activity fail with Binary XML file line #31: Error inflating class androidx.fragment.app.FragmentContainerView as shown in logcat below
Logcat & error
Here is the full logcat, we can see the the second sout is missing, but with no error thrown at this point.
The problem here came from the order of creation call
We can see it in the corrected code logcat
The activity onCreate is called first, but the first inflate call the fragment OnCreate, before resuming the activity OnCreate.
So every variable used in the fragment onCreate should be initialized before inflate call
In my app, there is an activity which needs to be closed on navigation click. In the past, the code below worked perfectly, however, since the new Android changes it doesnt work anymore.
Are there new ways to call the Navigation icon click? Is there something i am missing?
ActivitySettings.kt
class ActivitySettings : AppCompatActivity() {
private lateinit var binder: ActivitySettingsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binder = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(R.layout.activity_settings)
binder.topToolbarBack.setNavigationOnClickListener {
finish()
}
}
}
activity_settings.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="#+id/topToolbarBack"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:navigationIconTint="#color/white_material"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="#drawable/ic_outline_arrow_back_24"
app:popupTheme="#style/popupMenuThemeDark"
app:title="#string/currentScreenSettings"
app:titleTextColor="#color/white_material" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvSettings"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="#+id/topToolbarBack" />
</androidx.constraintlayout.widget.ConstraintLayout>
please make sure you call setSupportActionBar(binder.topToolbarBack) before you set click listener for the toolbar
This problem is caused by the way the layouts got called.
binder can be considered a "secondary layout". Since in that activity, i set as ContenteView R.layout.activity_settings, the binder, that can still get the elements from the layout, gets basically covered by the layout currently set.
If you want to use binder you must set as inside setContentView() binder.root, which loads the proper layout you need.
So, the code fixed is:
class ActivitySettings : AppCompatActivity() {
private lateinit var binder: ActivitySettingsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binder = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binder.root)
binder.topToolbarBack.setNavigationOnClickListener {
finish()
}
}
}
I study the google example at https://github.com/android/architecture-components-samples/tree/master/NavigationAdvancedSample .And it works like this :
But I need the about fragment to take the whole screen.What is best practice?
I have try this :
activity?.supportFragmentManager?.beginTransaction()?.replace(R.id.root_activity,Detail())?.addToBackStack("About")?.commit()
and get this:
the activity_main.xml:
<LinearLayout 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:layout_width="match_parent"
android:id="#+id/root_activity"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.android.navigationadvancedsample.MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nav_host_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottom_nav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:menu="#menu/bottom_nav"/>
I can set activity?.findViewById<BottomNavigationView>(R.id.bottom_nav)?.visibility=View.GONE
or activity?.findViewById<BottomNavigationView>(R.id.bottom_nav)?.visibility=View.INVISIBLE
But when it comes back,it looks like this,I think this is not good way:
thanks!!
You have to toggle visibility of BottomNavigationView as shown below:
NavController.OnDestinationChangedListener destinationChangedListener = new NavController.OnDestinationChangedListener() {
#Override
public void onDestinationChanged(#NonNull NavController controller, #NonNull NavDestination destination, #Nullable Bundle arguments) {
if(destination.getId() == R.id.navigation_notifications){
navView.setVisibility(View.GONE);
}else{
navView.setVisibility(View.VISIBLE);
}
}
};
navController.addOnDestinationChangedListener(destinationChangedListener);
in case anybody has ths same question,I post my answer here.
to work around ,I put BottomNavigationView into a MainFragment instead of MainActivity . And in the MainActivity dynamically instantiate the MainFragment,so I can replace the MainFragment as a whole in my deep child fragment .
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val container = FrameLayout(this)
container.id = R.id.activity_container
setContentView(container)
supportFragmentManager.beginTransaction().replace(R.id.activity_container,MainFragment()).commit()
}
}
in the nested fragment :
activity?.supportFragmentManager?.beginTransaction()?.replace(R.id.activity_container,About())?.addToBackStack("Abouts")?.commit()
final effect(have strip away the toolbar):
I have an activity with a CoordinatorLayout which contains a CollapsingToolbarLayout. I have bind the title of the activity to the title property of CollapsingToolbarLayout, like shown below(posting only relevant parts of code since the layout is too big):
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable name="viewModel"
type="com.android.myapp.ui.activity.home.HomeActivityViewModel"/>
</data>
<RelativeLayout 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:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.drawerlayout.widget.DrawerLayout
android:id="#+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusableInTouchMode="true">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="#+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.android.myapp.ui.behavior.scroll.appbar.DisableableAppBarLayoutBehavior"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:title="#{viewModel.pageTitle}"
app:collapsedTitleTextAppearance="#style/CollapsedAppBarTitleStyle"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleGravity="bottom|center_horizontal"
app:expandedTitleMarginBottom="85dp"
app:expandedTitleTextAppearance="#style/ExpandedAppBarTitleStyle"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<!--Remaining layout-->
</layout>
As you can see the pageTitle property is bind to the xml CollapsingToolbarLayout title property.
Now in the HomeActivityViewModel:
class HomeActivityViewModel(application: Application) : AndroidViewModel(application) {
var isToolbarExpandable = MutableLiveData<Boolean>() // For handling the expansion of toolbar on navigation. This works perfectly
var pageTitle = MutableLiveData<String>()
init {
isToolbarExpandable.value = true
pageTitle.value = "Home"
}
}
And in HomeActivity:
class HomeActivity : AppCompatActivity() {
private lateinit var homeViewModel: HomeActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivitySafaricomHomeBinding>(this, R.layout.activity_safaricom_home)
binding.lifecycleOwner = this
setSupportActionBar(toolbar)
homeViewModel = getHomeViewModel()
binding.viewModel = homeViewModel
}
private fun getHomeViewModel(): HomeActivityViewModel = ViewModelProviders.of(this).get(HomeActivityViewModel::class.java)
}
The page title is set correctly in this case. But the trouble starts when I try setting the title anytime after the Activity and the first child fragment is created(I am using NavHostFragment, so the first fragment is created and attached as soon as the activity is created). The only place I can change the the title is the onCreate on activity and onCreate life cycle methods of start fragment. I am using the viewmodel as a shared viewmodel to change title from all child fragments. I am getting the ViewModel in Fragments using:
ViewModelProviders.of(activity!!).get(HomeActivityViewModel::class.java)
After that, whenever I try to change the title, it simple won't change. Whether it be on a button click, after a delay, on the next fragment, nothing works. The only working place is onCreate of Activity till onCreateView of first fragment. I added an observer for pageTitle in HomeActivity, but after the first change, even the observer is not being triggered. On debugging, I could see that it is the same instance of viewmodel that is retrieved, and the pageTitle is being changed actually, but it simply stops observing after creation. Any idea what is the issue here? Please help.
LiveData doesn't work with DataBinding since it doesn't have a lifecycle.
After
binding.viewModel = homeViewModel
Try Adding
binding.lifecycleOwner = this
So, all together
binding.viewModel = homeViewModel
binding.lifecycleOwner = this
I'm trying to abstract my Navigation Drawer to streamline my Android app but I'm running into some issues. The Nav Drawer simply doesn't respond to touches, I can't figure out why.
MainActivity.kt
class MainActivity : NavActivity() {
/** ON CREATE **/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
main_seekbar_price!!.setOnSeekBarChangeListener(this)
mDrawerLayout = findViewById(R.id.drawer_layout) // Variable in superclass
...
NavDrawer.kt
abstract class NavActivity : BaseActivity() {
/** Variables **/
lateinit var mDrawerLayout: DrawerLayout
/** ON CREATE **/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_nav)
// Set Nav Drawer listener
nav_view.setNavigationItemSelectedListener { menuItem ->
Log.d("NAV", "nav selected listener header")
menuItem.isChecked = true
mDrawerLayout.closeDrawers()
navMenuSwitch(menuItem)
true
}
// Set Nav Footer listener
nav_footer.setNavigationItemSelectedListener { menuItem ->
Log.d("NAV", "nav selected listener footer")
menuItem.isChecked = false
mDrawerLayout.closeDrawers()
navMenuSwitch(menuItem)
true
}
}
...
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout 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/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorBackground"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<FrameLayout
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" >
...
</FrameLayout>
<include layout="#layout/activity_nav" />
</android.support.v4.widget.DrawerLayout>
activity_nav.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.NavigationView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:menu="#menu/drawer_header"
app:headerLayout="#layout/nav_header">
<android.support.design.widget.NavigationView
android:id="#+id/nav_footer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:menu="#menu/drawer_footer">
</android.support.design.widget.NavigationView>
</android.support.design.widget.NavigationView>
I thought it has something to do with assigning mDrawerLayout in the child class but that didn't fix anything. It never calls the listeners though, no matter how much you click it it won't even print Logs in that listener.
Any help is welcome as I can't figure this out for the life of me, thank you in advance!
Four things happen, in this order, when your MainActivity's onCreate() is called:
MainActivity's content view is set by inflating activity_nav.xml.
The nav_view and nav_footer Kotlin variables are assigned by searching the content view for them. (Kotlin does this for you, so it's easy to forget that it's happening.)
Listeners are attached to the nav_view and nav_footer views.
MainActivity's content view is set again, this time by inflating activity_main.xml. (The include tag inside activity_main.xml tells the framework that it should inflate another copy of activity_nav.xml as part of the process.) The old content view is overwritten.
So by the time MainActivity.onCreate() finishes, the views with listeners attached are gone. One way to fix the problem would be to delete the line setContentView(R.layout.activity_nav) from NavActivity.onCreate() and to move the rest into an initNavDrawerListeners() method that's called from MainActivity.onCreate().