在Android中,我应该如何从Fragment传递Context对象



我最近学习了SQLite,了解了MVVM的基本知识。试图用这两个构建一个简单的记事本应用程序。为了使用SQLiteOpenHelper类创建数据库,我知道我们需要一个Context对象。我遇到了如何将Context对象从Fragment传递到Repository类的问题。以下是类别:

主活动.kt

package com.kotlin.thenotepadapplication.view
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import androidx.appcompat.widget.Toolbar
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.kotlin.thenotepadapplication.R
class MainActivity : AppCompatActivity(), View.OnClickListener {
private lateinit var activityMainToolbar: Toolbar
private lateinit var activityMainRecyclerView: RecyclerView
private lateinit var activityMainConstraintLayout: ConstraintLayout
private lateinit var activityMainFragmentConstraintLayout: ConstraintLayout
private lateinit var activityMainFloatingActionButton: FloatingActionButton
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initializeWidgets()
initializeToolbar(R.string.app_name)
setOnClickListenerMethod()
}
/**Governing method that overlooks all fragment transactions taking place
* First, it makes the current ConstraintLayout invisible while bringing the ConstraintLayout designated for the fragments into view.
* Next, depending on the function to be performed, it then segregates the work functions in:
* 1. the initializeToolbar() method
* 2. the performFragmentTransactionMethod() method*/
private fun initializeFragmentTransactions(fragment: Fragment, toolbarTitle: Int) {
activityMainConstraintLayout.visibility = View.INVISIBLE
activityMainFragmentConstraintLayout.visibility = View.VISIBLE
initializeToolbar(toolbarTitle)
performFragmentTransactionMethod(fragment)
}
/**The performFragmentTransactionMethod() is charged simply with changing the current fragment that's present.
* It takes in the required fragment to be attached as an argument and then changes to it.*/
private fun performFragmentTransactionMethod(fragment: Fragment) {
val fragmentManager: FragmentManager = supportFragmentManager
val fragmentTransaction: FragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.add(R.id.activity_main_fragment_constraint_layout, fragment)
fragmentTransaction.commit()
}
/**Method to initialize the widgets present in the View*/
private fun initializeWidgets() {
activityMainToolbar = findViewById(R.id.activity_main_toolbar_layout)
activityMainRecyclerView = findViewById(R.id.activity_main_recycler_view)
activityMainFloatingActionButton = findViewById(R.id.activity_main_floating_action_button)
activityMainConstraintLayout = findViewById(R.id.activity_main_constraint_layout)
activityMainFragmentConstraintLayout =
findViewById(R.id.activity_main_fragment_constraint_layout)
}
/**Method to initialize the Activity toolbar*/
private fun initializeToolbar(toolbarTitle: Int) {
setSupportActionBar(activityMainToolbar)
supportActionBar!!.setTitle(toolbarTitle)
}
/**Method to set the onClickListeners for all the required views in the application.*/
private fun setOnClickListenerMethod() {
activityMainFloatingActionButton.setOnClickListener(this)
}
/**Method to intercept all the clicks performed in the current View*/
override fun onClick(view: View?) {
if (view == activityMainFloatingActionButton) {
val addEditFragment = AddEditFragment()
val titleString: Int = R.string.new_note_string
initializeFragmentTransactions(addEditFragment, titleString)
}
}
/**Function that checks what to when the back button is pressed.
* If any fragment is active, it simply means that the activityMainFragmentConstraintLayout is invisible.
* Therefore, pressing the back button should bring the user back to the home page of the application.
* In the other case, i.e., the user is present on the main page, the app should exit. */
override fun onBackPressed() {
if (activityMainConstraintLayout.visibility == View.INVISIBLE) {
activityMainFragmentConstraintLayout.visibility = View.INVISIBLE
activityMainConstraintLayout.visibility = View.VISIBLE
initializeToolbar(R.string.app_name)
} else {
super.onBackPressed()
}
}
companion object {
private const val TAG = "MainActivity"
}
}

AddEditFragment.kt

package com.kotlin.thenotepadapplication.view
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.kotlin.thenotepadapplication.R
import com.kotlin.thenotepadapplication.model.NotepadEntryPOJO
import com.kotlin.thenotepadapplication.viewmodel.MainActivityViewModel
import java.text.SimpleDateFormat
import java.util.*
class AddEditFragment : Fragment(), View.OnClickListener {
private lateinit var fragmentAddEditSaveButton: Button
private lateinit var fragmentAddEditTitleTextView: TextView
private lateinit var fragmentAddEditSubtitleTextView: TextView
private lateinit var mainActivityViewModel: MainActivityViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view: View = inflater.inflate(R.layout.fragment_add_edit, container, false)
initializeWidgets(view)
setOnClickListenerMethod()
return view
}
private fun initiateSaveMethod(notepadEntryPOJO: NotepadEntryPOJO) {
Log.d(TAG, "initiateSaveMethod: Started")
mainActivityViewModel.insertDataIntoDatabase(notepadEntryPOJO).observe(this, {
Toast.makeText(context, it.toString(), Toast.LENGTH_SHORT).show()
})
}
/**OnClick method that handles all the clicks performed in the current view.*/
override fun onClick(view: View?) {
if (view == fragmentAddEditSaveButton) {
val titleString: String = fragmentAddEditTitleTextView.text.toString()
val subtitleString: String = fragmentAddEditSubtitleTextView.text.toString()
val dateString: String = getDateMethod()
initiateSaveMethod(NotepadEntryPOJO(titleString, subtitleString, dateString))
}
}
/**Method to get the date using the SimpleDateFormat class*/
private fun getDateMethod(): String {
return SimpleDateFormat("dd-MM-yy", Locale.US).format(Date())
}
/**Method to initializeWidgets present in the Fragment.*/
private fun initializeWidgets(view: View) {
fragmentAddEditSaveButton = view.findViewById(R.id.add_edit_fragment_save_button)
fragmentAddEditTitleTextView = view.findViewById(R.id.add_edit_fragment_title_edit_text)
fragmentAddEditSubtitleTextView =
view.findViewById(R.id.add_edit_fragment_subtitle_edit_text)
mainActivityViewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
}
/**Method to intercept all the clicks performed in the current View*/
private fun setOnClickListenerMethod() {
fragmentAddEditSaveButton.setOnClickListener(this)
}
companion object {
private const val TAG = "AddEditFragment"
}
}

MainActivityViewModel.kt

package com.kotlin.thenotepadapplication.viewmodel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.kotlin.thenotepadapplication.model.NotepadEntryPOJO
import com.kotlin.thenotepadapplication.repository.DatabaseRepository
class MainActivityViewModel : ViewModel() {
private val databaseRepository: DatabaseRepository = DatabaseRepository()
fun insertDataIntoDatabase(notepadEntryPOJO: NotepadEntryPOJO): MutableLiveData<Long> {
databaseRepository.insertMethod(notepadEntryPOJO)
return databaseRepository.returnMutableLiveData()
}
}

DatabaseRepository.kt

package com.kotlin.thenotepadapplication.repository
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.provider.BaseColumns
import androidx.lifecycle.MutableLiveData
import com.kotlin.thenotepadapplication.model.NotepadContract
import com.kotlin.thenotepadapplication.model.NotepadEntryDatabaseHelper
import com.kotlin.thenotepadapplication.model.NotepadEntryPOJO
/**The DatabaseRepository class is tasked with managing all the Database operations.
* The repository class isn't usually included with the MVVM architecture, but it recommended for best practices.
* The ViewModel will be communicating with this Repository class to perform all the operations.*/
class DatabaseRepository {
private val notepadEntryDatabaseHelper: NotepadEntryDatabaseHelper = NotepadEntryDatabaseHelper()
private val rowMutableLiveData: MutableLiveData<Long> = MutableLiveData()
/**Method used for performing inserting values into the database */
fun insertMethod(notepadEntryPOJO: NotepadEntryPOJO) {
val sqliteDatabase: SQLiteDatabase = notepadEntryDatabaseHelper.writableDatabase
val insertValues: ContentValues = ContentValues().apply {
put(NotepadContract.NotepadEntry.COLUMN_TITLE, notepadEntryPOJO.title)
put(NotepadContract.NotepadEntry.COLUMN_SUBTITLE, notepadEntryPOJO.subtitle)
put(NotepadContract.NotepadEntry.COLUMN_DATE, notepadEntryPOJO.date)
}
/*Insert queries usually return a long value signifying the row in which the value has been inserted.
* The calling method for the insertMethod() will display this returned value as a Toast.*/
val rowID: Long = sqliteDatabase.insert(
NotepadContract.NotepadEntry.TABLE_NAME,
null,
insertValues
)
rowMutableLiveData.postValue(rowID)
}
fun returnMutableLiveData(): MutableLiveData<Long>{
return rowMutableLiveData
}
/**Method for querying all the details present in the database.
* The details are extracted in a cursor format that is sent to the cursorParseMethod() for extraction.
* The cursorParseMethod() then returns an ArrayList of type NotepadEntryPOJO to this current method.
* This method then, promptly, returns the same ArrayList.*/
fun queryMethod(): ArrayList<NotepadEntryPOJO> {
val sqLiteDatabase: SQLiteDatabase = notepadEntryDatabaseHelper.readableDatabase
val sortOrder = "${BaseColumns._ID} DESC"
/*The projection specifies the exact columns from which we want to extract data from the database.
* In this case, the column ID is being omitted as it isn't quite usable in the Views.*/
val projection = arrayOf(
NotepadContract.NotepadEntry.TABLE_NAME,
NotepadContract.NotepadEntry.COLUMN_TITLE,
NotepadContract.NotepadEntry.COLUMN_DATE
)
/*The cursor object extract only the columns as specified in the projection array.
* In this case, it extracts all the columns with the exception of the ID column.
* The ID column is being used only for sorting the values into a descending order.*/
val cursor: Cursor = sqLiteDatabase.query(
NotepadContract.NotepadEntry.TABLE_NAME,
projection,
null,
null,
null,
null,
sortOrder
)
return cursorParseMethod(cursor)
}
/**The cursorParseMethod() takes an argument of type Cursor.
* It queries this cursor object and extract all the files into a separate ArrayList.
* This ArrayList is sent back to the query method.*/
private fun cursorParseMethod(cursor: Cursor): ArrayList<NotepadEntryPOJO> {
val databaseItems: ArrayList<NotepadEntryPOJO> = ArrayList()
with(cursor) {
while (cursor.moveToNext()) {
val titleItem = getString(getColumnIndex(NotepadContract.NotepadEntry.COLUMN_TITLE))
val subtitleItem =
getString(getColumnIndex(NotepadContract.NotepadEntry.COLUMN_SUBTITLE))
val dateItem = getString(getColumnIndex(NotepadContract.NotepadEntry.COLUMN_DATE))
databaseItems.add(NotepadEntryPOJO(titleItem, subtitleItem, dateItem))
}
}
return databaseItems
}
}

NotepadEntryDatabaseHelper.kt

package com.kotlin.thenotepadapplication.model
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
/**The DatabaseHelper class used for initializing for performing initial database operations*/
class NotepadEntryDatabaseHelper(context: Context) : SQLiteOpenHelper(
context,
NotepadContract.DATABASE_NAME,
null,
NotepadContract.DATABASE_VERSION
) {
/**The onCreate method is used for initially creating the database.*/
override fun onCreate(db: SQLiteDatabase?) {
db!!.execSQL(NotepadContract.SQLITE_CREATE_ENTRY)
}
/**The onUpgrade is used when we want to upgrade database.
* Usually upgrade refers to when we add more columns or remove them and other such operations.*/
override fun onUpgrade(p0: SQLiteDatabase?, p1: Int, p2: Int) {
}
}

目前,生产线:

private val notepadEntryDatabaseHelper: NotepadEntryDatabaseHelper = NotepadEntryDatabaseHelper()

显示了一个错误,因为没有传入Context对象。我在想我怎么能做到这一点。我考虑在DatabaseRepository.kt类中创建一个方法,每当我们需要它时都会初始化对象,即在insertMethod((和queryMethod((的开头初始化对象。

但我也对此感到担忧,特别是,这不会导致创建太多对象吗?

我想提到的另一件事是,我试图通过为MainActivityViewModel类创建构造函数来传递Context对象。但它不断地引起一个错误,即:

java.lang.RuntimeException: Cannot create an instance of class com.kotlin.thenotepadapplication.viewmodel.MainActivityViewModel

我认为这是因为当我在Fragment中创建ViewModel类的实例时,即在行中

mainActivityViewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)

我实际上并没有传递Context对象的构造函数参数。因此,应用程序每次都会崩溃。

如果需要将contextViewModel传递到Repository,请使用可以接受applicationContext的AndroidViewModel

也可以在这里查看android文档。

所以做出这些改变

ViewModel类:

class MainActivityViewModel(
application: Application
) : AndroidViewModel(application) {
// Access context from application, pass it to Repository
}

存储库类:

class DatabaseRepository(
context: Context
) { 
// Pass context to Helper class
NotepadEntryDatabaseHelper(context)
}

现在您需要这个类来创建AndroidViewModel的实例。

工厂等级:

class ActiityViewModelFactory(
private val application: Application
) :
ViewModelProvider.Factory {
@Suppress("unchecked_cast")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(YourViewModelClass::class.java)) {
return YourViewModelClass(application) as T
}
throw IllegalArgumentException("Cannot create instance for class ViewModelClass")
}
}

现在,在您的活动或片段中,您需要将工厂与viewModel一起传递,如下所示:

片段或活动类别:

val factory = YourViewModelFactory(application)
val viewModel = ViewModelProvider(this, factory).get(YourViewModelFactory::class.java)

希望这能奏效。如果你有错误,请告诉我。

我认为你应该像Dagger一样使用依赖注入。当你像这样初始化时,你会绑定一个上下文:

@Singleton
@Component(modules = [AppModule::class,ViewModelModule::class])
interface AppComponent{
@Component.Factory
interface  Factory{
fun create(@BindsInstance context: Context): AppComponent
}
fun inject(mainActivity: MainActivity)
fun inject(categoryFragment: CategoryFragment)
fun inject(categoriesFragment: CategoriesFragment)
fun inject(expensesFragment: ExpensesFragment)
fun inject(listExpensesFragment: ListExpensesFragment)
fun inject(reportFragment: ReportFragment)
}

下面是创建数据库实例的方法

@Module
class AppModule {
@Singleton
@Provides
fun provideDatabase(context: Context): ExpenseTrackerDB {
return Room.databaseBuilder(context, ExpenseTrackerDB::class.java, Constant.DB_NAME)
.fallbackToDestructiveMigration().build()
}
@Singleton
@Provides
fun provideCategoryDao(db: ExpenseTrackerDB): CategoryDao {
return db.categoryDao()
}
@Singleton
@Provides
fun provideExpenseDao(db: ExpenseTrackerDB): ExpenseDao {
return db.expenseDao()
}
}

以下是绑定上下文的方法

open class ExpenseTrackerApplication: Application() {
open val appComponent by lazy {
DaggerAppComponent.factory().create(applicationContext)
}
}

我构建了一个示例,请在此处查看:https://github.com/frank-nhatvm/expensestracker

还有另一种方法:使用AndroidViewModel。但你不应该用

相关内容

  • 没有找到相关文章

最新更新