0

So I was learning Manual Dependency Injection from the Android Developers documentation to implement a simple saving mechanism using the Room database library. However, it has been causing the NullPointerException in the Container class:

2021-03-15 07:12:08.370 23721-23721/com.arpansircar.Practice.dependencyinjectionpractice E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.arpansircar.Practice.dependencyinjectionpractice, PID: 23721
    java.lang.RuntimeException: Unable to instantiate application com.arpansircar.practice.dependencyinjectionpractice.di.MyApplication: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.Context android.content.Context.getApplicationContext()' on a null object reference
        at android.app.LoadedApk.makeApplication(LoadedApk.java:1230)
        at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6583)
        at android.app.ActivityThread.access$1400(ActivityThread.java:227)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1890)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:224)
        at android.app.ActivityThread.main(ActivityThread.java:7592)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
     

Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.Context android.content.Context.getApplicationContext()' on a null object reference
            at android.content.ContextWrapper.getApplicationContext(ContextWrapper.java:128)
            at com.arpansircar.practice.dependencyinjectionpractice.di.MyApplication.<init>(MyApplication.kt:7)
            at java.lang.Class.newInstance(Native Method)
            at android.app.AppComponentFactory.instantiateApplication(AppComponentFactory.java:76)
            at androidx.core.app.CoreComponentFactory.instantiateApplication(CoreComponentFactory.java:52)
            at android.app.Instrumentation.newApplication(Instrumentation.java:1156)
            at android.app.LoadedApk.makeApplication(LoadedApk.java:1222)
            at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6583) 
            at android.app.ActivityThread.access$1400(ActivityThread.java:227) 
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1890) 
            at android.os.Handler.dispatchMessage(Handler.java:107) 
            at android.os.Looper.loop(Looper.java:224) 
            at android.app.ActivityThread.main(ActivityThread.java:7592) 
            at java.lang.reflect.Method.invoke(Native Method) 
            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539) 
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950) 

Here are the classes where I figure there could be an error.

AppContainer.kt

package com.arpansircar.practice.dependencyinjectionpractice.di

import android.content.Context
import com.arpansircar.practice.dependencyinjectionpractice.repository.UserRepository
import com.arpansircar.practice.dependencyinjectionpractice.room.UserDao
import com.arpansircar.practice.dependencyinjectionpractice.room.UserDatabase
import com.arpansircar.practice.dependencyinjectionpractice.viewmodel.UserViewModelFactory

class AppContainer(context: Context) {

    private val userDao: UserDao = UserDatabase.getInstance(context).userDao()

    private val userRepository: UserRepository = UserRepository(userDao)

    val userViewModelFactory: UserViewModelFactory = UserViewModelFactory(userRepository)

}

MyApplication.kt

package com.arpansircar.practice.dependencyinjectionpractice.di

import android.app.Application

class MyApplication : Application() {

    val appContainer = AppContainer(applicationContext)

}

MainActivity.kt

package com.arpansircar.practice.dependencyinjectionpractice.view

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import com.arpansircar.practice.dependencyinjectionpractice.di.AppContainer
import com.arpansircar.practice.dependencyinjectionpractice.di.MyApplication
import com.arpansircar.practice.dependencyinjectionpractice.R

class MainActivity : AppCompatActivity() {

    private var appContainer: AppContainer? = null
    private lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        appContainer = (application as MyApplication).appContainer

        val navHostFragment: NavHostFragment =
            supportFragmentManager.findFragmentById(R.id.nav_host_fragment_container) as NavHostFragment
        navController = navHostFragment.findNavController()
    }

    override fun onDestroy() {
        super.onDestroy()
        appContainer = null
    }

}

RegistrationFragment.kt

package com.arpansircar.practice.dependencyinjectionpractice.view

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.arpansircar.practice.dependencyinjectionpractice.databinding.FragmentRegistrationBinding
import com.arpansircar.practice.dependencyinjectionpractice.di.MyApplication
import com.arpansircar.practice.dependencyinjectionpractice.room.UserEntity
import com.arpansircar.practice.dependencyinjectionpractice.viewmodel.UserViewModel

class RegistrationFragment : Fragment(), View.OnClickListener {

    private lateinit var binding: FragmentRegistrationBinding
    private lateinit var userViewModel: UserViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val appContainer = (context as MyApplication).appContainer

        userViewModel = ViewModelProvider(
            this,
            appContainer.userViewModelFactory
        ).get(UserViewModel::class.java)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentRegistrationBinding.inflate(
            inflater,
            container,
            false
        )

        return binding.root
    }

    override fun onStart() {
        super.onStart()
        binding.createAccountButton.setOnClickListener(this)
        binding.getAllAccountsButton.setOnClickListener(this)
    }

    override fun onClick(view: View?) {
        when (view) {

            binding.createAccountButton -> {
                val name: String = binding.userNameTextInputEditText.text.toString()
                val userName: String = binding.userUsernameTextInputEditText.text.toString()
                val password: String = binding.userPasswordTextInputEditText.text.toString()

                val userEntity = UserEntity(0, name, userName, password)
                userViewModel.insertUsers(userEntity)
            }

            binding.getAllAccountsButton -> {

            }
        }
    }
}

UserViewModelFactory.kt

package com.arpansircar.practice.dependencyinjectionpractice.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.arpansircar.practice.dependencyinjectionpractice.repository.UserRepository

class UserViewModelFactory(private val userRepository: UserRepository) : ViewModelProvider.Factory {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(UserViewModel::class.java)) {
            return UserViewModel(userRepository) as T
        }

        throw IllegalArgumentException("Unknown ViewModel Class")
    }

}

UserViewModel.kt

package com.arpansircar.practice.dependencyinjectionpractice.viewmodel

import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.arpansircar.practice.dependencyinjectionpractice.repository.UserRepository
import com.arpansircar.practice.dependencyinjectionpractice.room.UserEntity
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch

class UserViewModel(private val userRepository: UserRepository) : ViewModel() {

    fun insertUsers(userEntity: UserEntity) {
        viewModelScope.launch(IO) {
            userRepository.insertUser(userEntity)
        }
    }

    fun getUsers(): LiveData<List<UserEntity>> {
        return userRepository.selectUsers()
    }

}

Specifically, the error points to line no. 7 in the MyApplication.kt class

val appContainer = AppContainer(applicationContext)

stating

Attempt to invoke virtual method 'android.content.Contextandroid.content.Context.getApplicationContext()' on a null object reference

I can't figure out how the context can be null considering that I'm invoking the MyApplication class in the onCreate method of my MainActivity.kt class

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        appContainer = (application as MyApplication).appContainer
...
}

and in the onCreate method of my RegistrationFragment.kt class

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val appContainer = (context as MyApplication).appContainer

userViewModel = ViewModelProvider(
    this,
    appContainer.userViewModelFactory
).get(UserViewModel::class.java)

}

I mean, shouldn't the activity and fragment been started and initialized with a context within these methods?

Arpan Sircar
  • 545
  • 2
  • 4
  • 15

2 Answers2

1
class MyApplication : Application() {
    val appContainer = AppContainer(applicationContext)
}

You're trying to use your Application as a Context too early, at init phase before it is fully set up.

In theory the Application here is the application context, so you could replace applicationContext with this but that would only move the same issue to your AppContainer where you're trying to use the Context at init phase.

Consider postponing the usage of Context. For example, you could wrap your appContainer in a lazy delegate that inits AppContainer on first access and not at init phase.

laalto
  • 150,114
  • 66
  • 286
  • 303
  • Worked like a charm. Although I had to replace the `val appContainer = (context as MyApplication).appContainer` with `val appContainer = (requireActivity.applicationcontext as MyApplication).appContainer` – Arpan Sircar Mar 15 '21 at 16:06
1

applicationContext will be null until the Application onCreate();

Fisrt , you could try code below:

    class MyApplication : Application() {
    var appContainer: Context? = null
    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        Log.d("TAG", "attachBaseContext: " + applicationContext)
    }

    override fun onCreate() {
        super.onCreate()
        Log.d("TAG", "onCreate: " + applicationContext)
    }
    }  

could get log:

     D/TAG: attachBaseContext: null
     D/TAG: onCreate: siger.kotest0.MyApplication@6d46e8e   

So you could use applicationContext in onCreate():

    var appContainer: Context? = null
    override fun onCreate() {
        super.onCreate()
        appContainer = AppContainer(base)
        Log.d("TAG", "onCreate: " + applicationContext)
    }
    } 
zhangxaochen
  • 32,744
  • 15
  • 77
  • 108
  • I see. Considering that our custom Application class inherits Application, we can easily use the lifecycle callback methods here too. And when I'm creating the object for the AppContainer inside the MyApplication class, I'm doing so before the `onCreate()` method has been triggered. As a result, there's a `NullPointerException`. Got it. – Arpan Sircar Mar 15 '21 at 16:12