Welcome to the 3rd part of the Kotlin File Explorer series. Good job reading files from a path in the previous tutorial. Right now our application takes a single path and displays the file/folders at the path. We want to make our application more interactive. Clicking a folder should display the content in that folder and clicking a file should open it in appropriate application.
Setting up Click Listeners
We want to listen whenever the user clicks on any folder or application in our MainActivity. If you have followed the series properly you might remember that we have already added Click Listeners on items in our FileRecyclerAdater in Tutorial 2 – Reading files from paths.
We will now add listener in FilesListFragment to pass on this information into MainActivity. Go ahead and create an interface named OnItemClickListener in FilesListFragment. Also create a corresponding lateinit variable for the same. lateinit in Kotlin is a great feature, it lets you define a non null variable telling Kotlin compiler that the variable will get a value which will be non-null before the variable is used, but make sure the variable gets a non-null value before usage, else your application will crash.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class FilesListFragment : Fragment() { private lateinit var mFilesAdapter: FilesRecyclerAdapter private lateinit var PATH: String private lateinit var mCallback: OnItemClickListener interface OnItemClickListener { fun onClick(fileModel: FileModel) fun onLongClick(fileModel: FileModel) } companion object { private const val ARG_PATH: String = "com.thetechnocafe.gurleensethi.kotlinfileexplorer.fileslist.path" fun build(block: Builder.() -> Unit) = Builder().apply(block).build() } ... } |
This interface will be implement by the MainActivity. In onAttach lets assign mCallback a value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
class FilesListFragment : Fragment() { private lateinit var mFilesAdapter: FilesRecyclerAdapter private lateinit var PATH: String private lateinit var mCallback: OnItemClickListener interface OnItemClickListener { fun onClick(fileModel: FileModel) fun onLongClick(fileModel: FileModel) } companion object { private const val ARG_PATH: String = "com.thetechnocafe.gurleensethi.kotlinfileexplorer.fileslist.path" fun build(block: Builder.() -> Unit) = Builder().apply(block).build() } class Builder { var path: String = "" fun build(): FilesListFragment { val fragment = FilesListFragment() val args = Bundle() args.putString(ARG_PATH, path) fragment.arguments = args; return fragment } } override fun onAttach(context: Context?) { super.onAttach(context) try { mCallback = context as OnItemClickListener } catch (e: Exception) { throw Exception("${context} should implement FilesListFragment.OnItemCLickListener") } } ... } |
In onAttach we get the reference of our MainActivity, we cast it to OnItemClickListener, if the cast fails we through an exception and crash the application. Now let’s implement this interface in MainActivity.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
class MainActivity : AppCompatActivity(), FilesListFragment.OnItemClickListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) window.decorView.systemUiVisibility = window.decorView.systemUiVisibility.or(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) setContentView(R.layout.activity_main) if (savedInstanceState == null) { val filesListFragment = FilesListFragment.build { path = Environment.getExternalStorageDirectory().absolutePath } supportFragmentManager.beginTransaction() .add(R.id.container, filesListFragment) .addToBackStack(Environment.getExternalStorageDirectory().absolutePath) .commit() } } override fun onClick(fileModel: FileModel) { } override fun onLongClick(fileModel: FileModel) { } } |
Adding new Fragment to stack
In onClick, if its a folder we want to create a new FilesListFragment with a new path and add it on top of our existing fragment, if its a file we want to send an intent to open the file in appropriate application.
In MainActivity, create a function named addFileFragment that will create a new FilesListFragment with a provided path and replace the current fragment with the new one.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
class MainActivity : AppCompatActivity(), FilesListFragment.OnItemClickListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) window.decorView.systemUiVisibility = window.decorView.systemUiVisibility.or(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) setContentView(R.layout.activity_main) if (savedInstanceState == null) { val filesListFragment = FilesListFragment.build { path = Environment.getExternalStorageDirectory().absolutePath } supportFragmentManager.beginTransaction() .add(R.id.container, filesListFragment) .addToBackStack(Environment.getExternalStorageDirectory().absolutePath) .commit() } } override fun onClick(fileModel: FileModel) { } override fun onLongClick(fileModel: FileModel) { } private fun addFileFragment(fileModel: FileModel) { val filesListFragment = FilesListFragment.build { path = fileModel.path } val fragmentTransaction = supportFragmentManager.beginTransaction() fragmentTransaction.replace(R.id.container, filesListFragment) fragmentTransaction.addToBackStack(fileModel.path) fragmentTransaction.commit() } } |
We create a new fragment from the provided path, replace the current fragment with new one and this fragment to back stack since we want to show the previous fragment if user presses the back button. In onClick, check if the FileType is Folder then call this function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
class MainActivity : AppCompatActivity(), FilesListFragment.OnItemClickListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) window.decorView.systemUiVisibility = window.decorView.systemUiVisibility.or(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) setContentView(R.layout.activity_main) if (savedInstanceState == null) { val filesListFragment = FilesListFragment.build { path = Environment.getExternalStorageDirectory().absolutePath } supportFragmentManager.beginTransaction() .add(R.id.container, filesListFragment) .addToBackStack(Environment.getExternalStorageDirectory().absolutePath) .commit() } } override fun onClick(fileModel: FileModel) { if (fileModel.fileType == FileType.FOLDER) { addFileFragment(fileModel) } } override fun onLongClick(fileModel: FileModel) { } private fun addFileFragment(fileModel: FileModel) { val filesListFragment = FilesListFragment.build { path = fileModel.path } val fragmentTransaction = supportFragmentManager.beginTransaction() fragmentTransaction.replace(R.id.container, filesListFragment) fragmentTransaction.addToBackStack(fileModel.path) fragmentTransaction.commit() } } |
Now lets handle when user presses back.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
class MainActivity : AppCompatActivity(), FilesListFragment.OnItemClickListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) window.decorView.systemUiVisibility = window.decorView.systemUiVisibility.or(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) setContentView(R.layout.activity_main) if (savedInstanceState == null) { val filesListFragment = FilesListFragment.build { path = Environment.getExternalStorageDirectory().absolutePath } supportFragmentManager.beginTransaction() .add(R.id.container, filesListFragment) .addToBackStack(Environment.getExternalStorageDirectory().absolutePath) .commit() } } override fun onClick(fileModel: FileModel) { if (fileModel.fileType == FileType.FOLDER) { addFileFragment(fileModel) } } override fun onLongClick(fileModel: FileModel) { } private fun addFileFragment(fileModel: FileModel) { val filesListFragment = FilesListFragment.build { path = fileModel.path } val fragmentTransaction = supportFragmentManager.beginTransaction() fragmentTransaction.replace(R.id.container, filesListFragment) fragmentTransaction.addToBackStack(fileModel.path) fragmentTransaction.commit() } override fun onBackPressed() { super.onBackPressed() if (supportFragmentManager.backStackEntryCount == 0) { finish() } } } |
When there are no more fragments on the stack, close the activity.
Never miss a post from TheTechnoCafe
Opening files on click
Opening files is very simple. All you have to do is fire an Intent with proper data in it. Let’s see how!
In the FileUtils.kt create an extension function on Context named launchFileIntent.
1 2 3 4 5 6 |
fun Context.launchFileIntent(fileModel: FileModel) { val intent = Intent(Intent.ACTION_VIEW) intent.data = FileProvider.getUriForFile(this, packageName, File(fileModel.path)) intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION startActivity(Intent.createChooser(intent, "Select Application")) } |
Intent.data is set to the Uri of the File that you are trying to share. We add a Intent.FLAG_GRANT_READ_URI_PERMISSION so that other applications can read the file from the Uri, if you don’t do this, other applications won’t be able to read the file.
Now call this function in onClick.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
class MainActivity : AppCompatActivity(), FilesListFragment.OnItemClickListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) window.decorView.systemUiVisibility = window.decorView.systemUiVisibility.or(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) setContentView(R.layout.activity_main) if (savedInstanceState == null) { val filesListFragment = FilesListFragment.build { path = Environment.getExternalStorageDirectory().absolutePath } supportFragmentManager.beginTransaction() .add(R.id.container, filesListFragment) .addToBackStack(Environment.getExternalStorageDirectory().absolutePath) .commit() } } override fun onClick(fileModel: FileModel) { if (fileModel.fileType == FileType.FOLDER) { addFileFragment(fileModel) } else { launchFileIntent(fileModel) } } override fun onLongClick(fileModel: FileModel) { } private fun addFileFragment(fileModel: FileModel) { val filesListFragment = FilesListFragment.build { path = fileModel.path } val fragmentTransaction = supportFragmentManager.beginTransaction() fragmentTransaction.replace(R.id.container, filesListFragment) fragmentTransaction.addToBackStack(fileModel.path) fragmentTransaction.commit() } override fun onBackPressed() { super.onBackPressed() if (supportFragmentManager.backStackEntryCount == 0) { finish() } } } |
In this next tutorial you will create breadcrumbs for easier navigation.
<< Previous Part 2 – Reading files from paths
1 Comment
Marek Šabo · October 12, 2018 at 6:35 am
There is a missing code in FilesListFragment#initView():
mFilesAdapter.onItemClickListener = {
mCallback.onClick(it)
}
mFilesAdapter.onItemLongClickListener = {
mCallback.onLongClick(it)
}
Taken from: https://github.com/gurleensethi/kotlin-file-explorer/blob/master/app/src/main/java/com/thetechnocafe/gurleensethi/kotlinfileexplorer/fileslist/FilesListFragment.kt