简体   繁体   中英

String resource not found in kotlin android with getPageTitle

New to android and kotlin here. Making my first app and I'm trying to use the getPageTitle function to give my tabs their titles (of which are string resources). The full implementation is as follows:

class FAAMainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    setSupportActionBar(toolbar as Toolbar)

    val pagerAdapter = SectionsPagerAdapter(supportFragmentManager, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)
    pager.adapter = pagerAdapter
    tabs.setupWithViewPager(pager)
}

private class SectionsPagerAdapter : FragmentPagerAdapter{

    constructor(fm: FragmentManager, behavior: Int) : super(fm, behavior)

    override fun getItem(position: Int): Fragment {
        when (position) {
            0 -> return HomeFragment()
            1 -> return KittensFragment()
            2 -> return CatsFragment()
            3 -> return FosterersFragment()
            4 -> return FAAUsersFragment()
            else -> {
                return HomeFragment()
            }
        }
    }

    override fun getCount(): Int {
        return 5
    }

    override fun getPageTitle(position: Int): CharSequence? {
        when(position) {
            0 ->  Resources.getSystem().getText(R.string.home_tab)
            1 ->  Resources.getSystem().getText(R.string.kitten_tab)
            2 ->  Resources.getSystem().getText(R.string.cat_tab)
            3 ->  Resources.getSystem().getText(R.string.fosterer_tab)
            4 ->  Resources.getSystem().getText(R.string.faa_user_tab)
            else -> "Error"
        }
        return "Error"
    }
}
}

Trying to run the application gives the following error:

java.lang.RuntimeException: Unable to start activity ComponentInfo{uk.ac.aber.dcs.cs31620.faa/uk.ac.aber.dcs.cs31620.faa.ui.FAAMainActivity}: android.content.res.Resources$NotFoundException: String resource ID #0x7f0d0027

I don't understand why it cannot find the String resource.

My strings.xml;

<resources>
    <string name="app_name">Feline Adoption Agency</string>
    <string name="hello_blank_fragment">Hello blank fragment</string>
    <string name="home_tab">Home</string>
    <string name="kitten_tab">Kittens</string>
    <string name="cat_tab">Cats</string>
    <string name="fosterer_tab">Fosterers</string>
    <string name="faa_user_tab">FAA Users</string>
</resources>

More information as I am putting a bounty on this:

  • I can verify the resources are being placed in the app/build/generated/not_namespaced_r_class_sources/debug/r/uk/ac/aber/dcs/cs31620/faa/R.java file correctly.
  • However they are not put into the app/build/generated/not_namespaced_r_class_sources/debug/r/androidx/appcompat/R.java and I am not sure that is the cause of the issue.
  • My imports for the FAAMainActivity class are:

    import android.content.res.Resources import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentPagerAdapter import kotlinx.android.synthetic.main.activity_main.* import uk.ac.aber.dcs.cs31620.faa.R import uk.ac.aber.dcs.cs31620.faa.ui.cats.CatsFragment import uk.ac.aber.dcs.cs31620.faa.ui.faa_users.FAAUsersFragment import uk.ac.aber.dcs.cs31620.faa.ui.fosterers.FosterersFragment import uk.ac.aber.dcs.cs31620.faa.ui.home.HomeFragment import uk.ac.aber.dcs.cs31620.faa.ui.kittens.KittensFragment

I've uploaded the project here if anyone want's to try it out.

Calling Resources.getSystem() provides System level resources and not your application level resources as per the doc:

Return a global shared Resources object that provides access to only system resources (no application resources), and is not configured for the current screen (can not use dimension units, does not change based n orientation, etc).

You'll need application or activity level context in order to retrieve the strings from your strings.xml. I have changed your SectionsPagerAdapter to this in order to fix your error:

private class SectionsPagerAdapter(fm: FragmentManager, behavior: Int, private val context: Context) :
        FragmentPagerAdapter(fm, behavior) {

        override fun getItem(position: Int): Fragment {
            return when (position) {
                0 -> HomeFragment()
                1 -> KittensFragment()
                2 -> CatsFragment()
                3 -> FosterersFragment()
                4 -> FAAUsersFragment()
                else -> {
                    HomeFragment()
                }
            }
        }

        override fun getCount(): Int {
            return 5
        }

        override fun getPageTitle(position: Int): CharSequence? {
           return when(position) {
                0 ->  context.getString(R.string.kitten_tab)
                1 ->  context.getString(R.string.kitten_tab)
                2 ->  context.getString(R.string.cat_tab)
                3 ->  context.getString(R.string.fosterer_tab)
                4 ->  context.getString(R.string.faa_user_tab)
               else -> context.getString(R.string.home_tab)
            }
        }
    }

FAAMainActivity

val pagerAdapter = SectionsPagerAdapter(supportFragmentManager, FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT, this) // Passed "this" as context

Here, I have primarily changed following things in your code,

From

private class SectionsPagerAdapter : FragmentPagerAdapter{

    constructor(fm: FragmentManager, behavior: Int) : super(fm, behavior)
    ...
}

To

private class SectionsPagerAdapter(fm: FragmentManager, behavior: Int, private val context: Context) :
        FragmentPagerAdapter(fm, behavior) {
        ...
}

I have changed your JAVA style constructor to kotlin's primary constructor and added Context as third parameter. Now we will use that Context to get the string instead of Resources.getSystem() .

Use

Resources.getSystem().getString(R.string.faa_user_tab)

instead of

Resources.getSystem().getText(R.string.faa_user_tab)

As stated in the documentation of Resources , the method getSystem() :

Return a global shared Resources object that provides access to only system resources (no application resources), is not configured for the current screen (can not use dimension units, does not change based on orientation, etc), and is not affected by Runtime Resource Overlay.

So you cannot access your application resources by this method. But looking at your code you can simply change

private class SectionsPagerAdapter

to:

private inner class SectionsPagerAdapter

and when you want to get the string instead of

Resources.getSystem().getString(R.string.kitten_tab)

do this:

getString(R.string.kitten_tab)

Explanation: When you mark a class as inner you are making every method along with other things in outer class visible and accessible in the inner class . In every Activity (any Context ) there are methods for accessing resources. One of them is getString which you can simply use to get your strings by their id.

I know I'm not answering your question directly but the issue can be solved by changing the way you create/use the adapter.

The main change is that instead of letting the adapter decide how to populate the view we give that "responsibilty" to the user.

We do that by requesting a list of pages, in this example I used a Pair<String, Fragment> but you can create an actual data model with all the necessary information you need.

class SectionsPagerAdapter(
    fragmentManager: FragmentManager,
    behavior: Int,
    val pages: List<Pair<String, Fragment>>
) : FragmentPagerAdapter(fragmentManager, behavior) {

    override fun getItem(position: Int): Fragment = pages[position].second

    override fun getCount(): Int = pages.size

    override fun getPageTitle(position: Int): CharSequence = pages[position].first
}

And when you are creating the adapter you create it inside your Activity/Fragment like this

val pages = listOf( /* Change the list to fit your exact needs*/
    context.getString(R.string.kitten_tab) to HomeFragment(),
    context.getString(R.string.kitten_tab) to KittensFragment(),
    context.getString(R.string.cat_tab) to CatsFragment(),
    context.getString(R.string.fosterer_tab) to FosterersFragment(),
    context.getString(R.string.faa_user_tab) to FAAUsersFragment()
)

val adapter = SectionsPagerAdapter(
    supportFragmentManager,
    FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT,
    pages
)

This way, you do not only solve the problem, but you also achive a scalable solution that supports not only this use case but any other use case

The code that works with your method are:

Resources.getSystem().getText(android.R.string.cancel)   // returns Cancel 
Resources.getSystem().getString(android.R.string.cancel) // returns Cancel 
Resources.getSystem().getResourceName(android.R.string.cancel)) //returns android:string/cancel
Resources.getSystem().getResourceEntryName(android.R.string.cancel)) returns cancel

But in all the cases the string used starts with android.R that means you can access only the androids built in resources using these methods.

I dont know why you are trying to access your string resource using the above method.

Below I have listed certains methods to access your app related resources:

getResources().getText(R.string.app_name)); // returns Test App
getResources().getString(R.string.app_name)); // returns Test App
getResources().getResourceEntryName(R.string.app_name)); // returns app_name
getResources().getResourceName(R.string.app_name)); //returns com.example.testapp:string/app_name
context.getText(R.string.app_name)); // returns Test App
context.getString(R.string.app_name)); // returns Test App

Also you can use the context to access the methods starting with getResources() as well.

I think you are calling the string resources in a wrong way. it should be something like this:-

override fun getPageTitle(position: Int): CharSequence? {
    when(position) {
        0 ->  getString(R.string.home_tab) // just call getString() bcz, calling from AppCompatActivity
        1 ->  getString(R.string.kitten_tab) // if you use fragment and try to call 
        2 ->  getString(R.string.cat_tab) // getString(), use resources.getString(...)
        3 ->  getString(R.string.fosterer_tab)
        4 ->  getString(R.string.faa_user_tab)
        else -> "Error"
    }
    return "Error"
}

Try this, hope your problem will be solved. Let me know if any issue arises further. Happy coding.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM