我应该如何从Android中的片段传递Context对象?

如何解决我应该如何从Android中的片段传递Context对象?

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

MainActivity.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,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,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对象的构造函数参数。结果,该应用每次都崩溃。

解决方法

如果您需要将context中的ViewModel传递到Repository,请使用AndroidViewModel,它可以使用applicationContext,也需要创建实例。

还要检查android documentation here

进行这些更改

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。但是你不应该使用它

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?