I want to write unit tests for this presenter class HomePresenter in Android Studio. However, I'm having trouble creating tests to cover both the geoQueryOnUserLocation method and the geoQueryOnSignLocation method due to using GeoFire and Firebase.
I want to test this class using Mockito. Is there a good way to test this class?
HomePresenter.kt:
class HomePresenter(val view: HomeView, val context: Context, val activity: FragmentActivity) {
private val mFirebaseDatabase = FirebaseDatabase.getInstance()
private val mDatabaseReference = mFirebaseDatabase.reference
private var mFirstSignKey: String? = "first_sign_key"
private var mLastSignKey: String? = "last_sign_key"
private var mGeoQueryOnSignLocation: GeoQuery? = null
private var mGeoQueryOnMyLocation: GeoQuery? = null
fun geoQueryOnUserLocation(lastLocation: Location?, radius: Double?, userAddress: String?) {
mGeoQueryOnMyLocation =
GeoFire(mDatabaseReference.child(CHILD_GEOFIRE_TYPE_1)).queryAtLocation(
GeoLocation(
lastLocation?.latitude!!,
lastLocation.longitude
), radius?.div(1000)!!
)
mGeoQueryOnMyLocation?.addGeoQueryEventListener(object : GeoQueryEventListener {
override fun onKeyEntered(key: String?, location: GeoLocation?) {
mFirstSignKey = key
if (mFirstSignKey != mLastSignKey) {
mDatabaseReference.child(CHILD_SIGNS).child(key!!)
.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
val sign = dataSnapshot.getValue(Sign::class.java)
if (sign?.address == userAddress) {
view.onRetrieveLastSign(sign, key, TAG_SIGN_CAT_1)
mLastSignKey = sign?.signId
}
}
override fun onCancelled(databaseError: DatabaseError) {
view.onFailure(databaseError.message, "")
}
})
}
}
override fun onGeoQueryError(error: DatabaseError) {
view.onFailure(error.message, "")
}
override fun onGeoQueryReady() {}
override fun onKeyMoved(key: String?, location: GeoLocation?) {}
override fun onKeyExited(key: String?) {}
})
}
fun geoQueryOnSignLocation(listSign: ArrayList<Sign>) {
if (mGeoQueryOnSignLocation != null) {
mGeoQueryOnSignLocation?.removeAllListeners()
}
listSign.iterator().forEach {
if (it.type == 0 || it.type == 1) {
mGeoQueryOnSignLocation =
GeoFire(mDatabaseReference.child(CHILD_GEOFIRE_TYPE_0)).queryAtLocation(
GeoLocation(it.latitude, it.longitude),
0.015
)
mGeoQueryOnSignLocation?.addGeoQueryEventListener(object : GeoQueryEventListener {
override fun onKeyEntered(key: String?, location: GeoLocation?) {
view.onRetrieveLastSign(it, key as String, TAG_SIGN_CAT_0_ENTERED)
}
override fun onKeyExited(key: String?) {
view.onRetrieveLastSign(it, key as String, TAG_SIGN_CAT_0_EXITED)
}
override fun onGeoQueryError(error: DatabaseError) {
view.onFailure(error.message, "")
}
override fun onKeyMoved(key: String?, location: GeoLocation?) {}
override fun onGeoQueryReady() {}
})
}
}
}
}
HomeFragment.kt:
class HomeFragment : Fragment(), HomeView {
private lateinit var mPresenter: HomePresenter
private var mUserLastLocation: Location? = null
private var mUserLastSpeed: Float = 0.0f
private var mUserLastRadius: Double? = null
override fun onRetrieveLastLocation(lastLocation: Location, address: String?) {
mUserLastLocation = lastLocation
mUserLastSpeed = mUserLastLocation?.speed!!
mUserLastRadius = mUserLastSpeed.times(mPrefNotificationTimeSignCat1 as Int).toDouble()
mPresenter.geoQueryOnUserLocation(mUserLastLocation, mUserLastRadius, address)
}
override fun onLoadAllSigns(signs: ArrayList<Sign>) {
mPresenter.updateSignLocationsToGeoFire(signs)
mPresenter.geoQueryOnSignLocation(signs)
}
override fun onRetrieveLastSign(sign: Sign?, key: String, tag: String) {
if (sign?.headingQuadrant != mUserLastLocation?.let { userBearingQuadrant(it) }) {
if (tag == TAG_SIGN_CAT_1) {
...
}
if (tag == TAG_SIGN_CAT_0_ENTERED) {
...
}
if (tag == TAG_SIGN_CAT_0_EXITED) {
...
}
}
}
private fun userBearingQuadrant(location: Location): String {
return if (location.bearing in 0.0..90.0) {
"I"
} else if (location.bearing > 90 && location.bearing <= 180) {
"II"
} else if (location.bearing > 180 && location.bearing <= 270) {
"III"
} else {
"IV"
}
}
}
Related
I want to move the braintree code from mainactivity to another class, but the DropInListener and AppCompatActivity() cause errors when I move them out of the mainactivity.
Part of the move is easy.
I can certainly create a class as such, but the problem is this class needs to inherit AppCompatActivity. When I do that, it creates a bigger project that i am not sure is the best way to do this. Ideally i would just like to instantiate the braintree class in the MainActivity, doesn't work either..
============================ Proposed class that does not work because of no AppCompatActivity=================
class Braintree constructor() : DropInListener {
**private lateinit var dropInClient: DropInClient
private lateinit var braintreeClient: BraintreeClient
private lateinit var dataCollector: DataCollector**
dropInClient = DropInClient(this#MainActivity, ExampleClientTokenProvider(commonRepository))
dropInClient.setListener(this#MainActivity)
braintreeClient = BraintreeClient(this#MainActivity, CLIENT_TOKEN)
dataCollector = DataCollector(braintreeClient)
}
fun loadDropIn() {
val dropInRequest = DropInRequest()
dropInClient.launchDropIn(dropInRequest)
}
// Function to check and request permission.
private fun checkPermission(permission: String, requestCode: Int) {
if (ContextCompat.checkSelfPermission(this#MainActivity, permission) == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(this#MainActivity, arrayOf(permission), requestCode)
} else {
Toast.makeText(this#MainActivity, "Permission already granted", Toast.LENGTH_SHORT).show()
}
}
private fun collectDeviceData(paymentMethodNonce: String?) {
dataCollector.collectDeviceData(MainActivity.INSTANCE) { deviceData, error ->
val deviceDataObj = Gson().fromJson(deviceData, DeviceData::class.java)
CoroutineScope(Dispatchers.IO).launch {
if (paymentMethodNonce != null) {
commonRepository.checkout(commonRepository.amount,
commonRepository.uuid, paymentMethodNonce, deviceDataObj.correlation_id)
}
}
}
}
override fun onDropInSuccess(dropInResult: DropInResult) {
Log.d("TAG2", "dropin success")
val paymentMethodNonce = dropInResult.paymentMethodNonce?.string
collectDeviceData(paymentMethodNonce)
}
override fun onDropInFailure(error: Exception) {
if (error is UserCanceledException) {
Log.d("TAG2", "dropin user cancelled : ${error.message}")
} else {
Log.d("TAG2", "dropin failure : ${error.message}")
}
}
}
============= Original code and want to move the items in bold out of the MainActivity class to a separate location/file/class/something=======================
#HiltAndroidApp
class MyApp #Inject constructor() : Application() {
init {
INSTANCE = this
}
companion object {
lateinit var INSTANCE: MyApp
private set
val applicationContext: Context
get() {
return INSTANCE.applicationContext
}
}
}
#AndroidEntryPoint
class MainActivity : AppCompatActivity(), DropInListener {
#Inject lateinit var commonRepository: CommonRepository
**private lateinit var dropInClient: DropInClient**
**private lateinit var braintreeClient: BraintreeClient**
**private lateinit var dataCollector: DataCollector**
init {
INSTANCE = this
}
companion object {
lateinit var INSTANCE: MainActivity
private set
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
SnoozeForGoodTheme(false) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
checkPermission(SCHEDULE_EXACT_ALARM, 1)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
checkPermission(USE_EXACT_ALARM, 1)
}
checkPermission(RECEIVE_BOOT_COMPLETED, 1)
checkPermission(INTERNET, 1)
NavHost(
navController = navController, startDestination = SplashDirections.root.destination) {
composable(route = SplashDirections.root.destination) {
Log.d("TAG2", "going splash page")
SplashContentFactory().Content(navController)
}
}
}
}
**dropInClient = DropInClient(this#MainActivity, ExampleClientTokenProvider(commonRepository))
dropInClient.setListener(this#MainActivity)
braintreeClient = BraintreeClient(this#MainActivity, CLIENT_TOKEN)
dataCollector = DataCollector(braintreeClient)
}
fun loadDropIn() {
val dropInRequest = DropInRequest()
dropInClient.launchDropIn(dropInRequest)
}
// Function to check and request permission.
private fun checkPermission(permission: String, requestCode: Int) {
if (ContextCompat.checkSelfPermission(this#MainActivity, permission) == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(this#MainActivity, arrayOf(permission), requestCode)
} else {
Toast.makeText(this#MainActivity, "Permission already granted", Toast.LENGTH_SHORT).show()
}
}
private fun collectDeviceData(paymentMethodNonce: String?) {
dataCollector.collectDeviceData(MainActivity.INSTANCE) { deviceData, error ->
val deviceDataObj = Gson().fromJson(deviceData, DeviceData::class.java)
CoroutineScope(Dispatchers.IO).launch {
if (paymentMethodNonce != null) {
commonRepository.checkout(commonRepository.amount,
commonRepository.uuid, paymentMethodNonce, deviceDataObj.correlation_id)
}
}
}
}
override fun onDropInSuccess(dropInResult: DropInResult) {
Log.d("TAG2", "dropin success")
val paymentMethodNonce = dropInResult.paymentMethodNonce?.string
collectDeviceData(paymentMethodNonce)
}
override fun onDropInFailure(error: Exception) {
if (error is UserCanceledException) {
Log.d("TAG2", "dropin user cancelled : ${error.message}")
} else {
Log.d("TAG2", "dropin failure : ${error.message}")
}
}**
}
I'm learning paging 3, but the data from the API doesn't appear. My code is like below:
interface PokeAPI {
#GET("pokemon")
fun getPokemonList() : Call<PokemonList>
#GET("pokemon")
fun getAllPokemon(
#Query("limit") limit: Int,
#Query("offset") offset: Int) : PokemonList
#GET("pokemon/{name}")
fun getPokemonInfo(
#Path("name") name: String
) : Call<Pokemon>
}
class PokePagingSource(private val apiService: PokeAPI): PagingSource<Int, Result>() {
private companion object {
const val INITIAL_PAGE_INDEX = 1
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Result> {
return try {
val position = params.key ?: INITIAL_PAGE_INDEX
val responseData = apiService.getAllPokemon(position, params.loadSize)
if (responseData.results.isEmpty()) {
Log.e("Response Succeed!", responseData.results.toString())
} else {
Log.e("Response Failed!", responseData.results.toString())
}
LoadResult.Page(
data = responseData.results,
prevKey = if (position == INITIAL_PAGE_INDEX) null else position - 1,
nextKey = if (responseData.results.isNullOrEmpty()) null else position + 1
)
} catch (exception: Exception) {
return LoadResult.Error(exception)
}
}
override fun getRefreshKey(state: PagingState<Int, Result>): Int? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
}
class PokemonRepository(private val apiService: PokeAPI) {
fun getAllPokemon(): LiveData<PagingData<Result>>{
return Pager(
config = PagingConfig(
pageSize = 10
),
pagingSourceFactory = {
PokePagingSource(apiService)
}
).liveData
}
}
object Injection {
private val api by lazy { RetrofitClient().endpoint }
fun provideRepository(): PokemonRepository {
return PokemonRepository(api)
}
}
class PokemonViewModel(pokemonRepository: PokemonRepository) : ViewModel() {
val allPokemonList: LiveData<PagingData<Result>> =
pokemonRepository.getAllPokemon().cachedIn(viewModelScope)
}
class ViewModelFactory : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(PokemonViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return PokemonViewModel(Injection.provideRepository()) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
`class PokemonPagingAdapter(private val context: Context) :
PagingDataAdapter<Result, PokemonPagingAdapter.ViewHolder>(DIFF_CALLBACK) {
private var onItemClick: OnAdapterListener? = null
fun setOnItemClick(onItemClick: OnAdapterListener) {
this.onItemClick = onItemClick
}
class ViewHolder(val binding: AdapterPokemonBinding) : RecyclerView.ViewHolder(binding.root) {
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
AdapterPokemonBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val pokemonData = getItem(position)
if (pokemonData != null) {
holder.binding.apply {
val number = if (pokemonData.url.endsWith("/")) {
pokemonData.url.dropLast(1).takeLastWhile { it.isDigit() }
} else {
pokemonData.url.takeLastWhile { it.isDigit() }
}
val url = "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${number}.png"
Glide.with(context)
.load(url)
.transition(DrawableTransitionOptions.withCrossFade())
.centerCrop()
.circleCrop()
.into(ivPokemon)
tvNamePokemon.text = pokemonData.name
btnDetail.setOnClickListener {
onItemClick?.onClick(pokemonData, pokemonData.name, url)
}
}
}
}
companion object {
val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Result>() {
override fun areItemsTheSame(
oldItem: Result,
newItem: Result
): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(
oldItem: Result,
newItem: Result
): Boolean {
return oldItem.name == newItem.name
}
}
}
interface OnAdapterListener {
fun onClick(data: Result, name: String, url: String)
}
}`
class FragmentPokemon: Fragment(R.layout.fragment_pokemon) {
private var _binding : FragmentPokemonBinding? = null
private val binding get() = _binding!!
private lateinit var dataPagingAdapter: PokemonPagingAdapter
private val viewModel: PokemonViewModel by viewModels {
ViewModelFactory()
}
private lateinit var comm: Communicator
override fun onStart() {
super.onStart()
getData()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentPokemonBinding.bind(view)
val toolBar = requireActivity().findViewById<View>(R.id.tool_bar)
toolBar.visibility = View.VISIBLE
val navBar = requireActivity().findViewById<BottomNavigationView>(R.id.bottom_navigation)
navBar.visibility = View.VISIBLE
comm = requireActivity() as Communicator
setupListPokemon()
}
private fun setupListPokemon(){
dataPagingAdapter = PokemonPagingAdapter(requireContext())
dataPagingAdapter.setOnItemClick(object: PokemonPagingAdapter.OnAdapterListener{
override fun onClick(data: Result, name: String, url: String) {
comm.passDataCom(name, url)
}
})
binding.apply {
rvPokemon.layoutManager = LinearLayoutManager(context)
rvPokemon.setHasFixedSize(true)
rvPokemon.adapter = dataPagingAdapter
}
}
private fun getData(){
viewModel.allPokemonList.observe(viewLifecycleOwner){
dataPagingAdapter.submitData(lifecycle, it)
binding.btnCoba.setOnClickListener { btn ->
if (it == null){
Log.e("ResponseFailed", it.toString())
} else Log.e("ResponseSucceed", it.toString())
}
}
}
}
What's the reason? I have followed the step by step implementation of paging 3 but the data still doesn't appear either.
I don't know the API you are using, but it seems that you are using it incorrectly. The getAllPokemon method has limit and offset parameters and you are calling it like apiService.getAllPokemon(position, params.loadSize), so you are using position as a limit and params.loadSize as an offset.
You should pass params.loadSize as a limit, rename INITIAL_PAGE_INDEX to INITIAL_OFFSET and set it to 0, since your API uses offsets instead of pages (at least it seems so from what you provided). The load function should then look something like this:
// get current offset
val offset = params.key ?: INITIAL_OFFSET
val responseData = apiService.getAllPokemon(limit = params.loadSize, offset = offset)
val prevKey = offset - params.loadSize
val nextKey = offset + params.loadSize
I have a way where when I enter the name of the city, I substitute the name of the city in the api, and everything works fine, but if I enter a non-real city, then nonsense is put in the api accordingly and the application crashes. I would like to make it so that if the city does not exist, that is, I output an error, then Toast "enter the correct name of the city" appears. Thank you in advance
Код:
const val USE_DEVICE_LOCATION = "USE_DEVICE_LOCATION"
const val CUSTOM_LOCATION = "CUSTOM_LOCATION"
class LocationProviderImpl(
private val fusedLocationProviderClient: FusedLocationProviderClient,
context: Context) : PreferenceProvider(context), LocationProvider {
private val appContext = context.applicationContext
override suspend fun hasLocationChanged(lastWeatherLocation: WeatherLocation): Boolean {
val deviceLocationChanged = try {
hasDeviceLocationChanged(lastWeatherLocation)
} catch (e: LocationPermissionNotGrantedException) {
false
}
return deviceLocationChanged || hasCustomLocationChanged(lastWeatherLocation)
}
override suspend fun getPreferredLocationString(): String {
if(isUsingDeviceLocation()) {
try {
val deviceLocation = getLastDeviceLocation()
?: return "${getCustomLocationName()}"
return "${deviceLocation.latitude},${deviceLocation.longitude}"
} catch (e: LocationPermissionNotGrantedException) {
return "${getCustomLocationName()}"
}
}
else {
return "${getCustomLocationName()}"
}
}
private suspend fun hasDeviceLocationChanged(lastWeatherLocation: WeatherLocation): Boolean {
if (!isUsingDeviceLocation())
return false
val deviceLocation = getLastDeviceLocation()
?: return false
// Comparing doubles cannot be done with "=="
val comparisonThreshold = 0.03
return Math.abs(deviceLocation.latitude - lastWeatherLocation.lat) > comparisonThreshold &&
Math.abs(deviceLocation.longitude - lastWeatherLocation.lon) > comparisonThreshold
}
private fun hasCustomLocationChanged(lastWeatherLocation: WeatherLocation): Boolean {
if (!isUsingDeviceLocation()) {
val customLocationName = getCustomLocationName()
return customLocationName != lastWeatherLocation.name
}
return false
}
private fun getCustomLocationName(): String? {
try {
return preferences.getString(CUSTOM_LOCATION, null)
}
catch (e: Exception) {
isUsingDeviceLocation()
}
throw LocationPermissionNotGrantedException()
}
private fun isUsingDeviceLocation(): Boolean {
return preferences.getBoolean(USE_DEVICE_LOCATION, true)
}
#SuppressLint("MissingPermission")
private suspend fun getLastDeviceLocation(): Location? {
return (if (hasLocationPermission())
fusedLocationProviderClient.lastLocation.asDeferred()
else {
throw LocationPermissionNotGrantedException()
}).await()
}
private fun hasLocationPermission(): Boolean {
return ContextCompat.checkSelfPermission(appContext,
android.Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
}
}
CurrentWeatherFragment
class CurrentWeatherFragment : ScopedFragment(), KodeinAware, SharedPreferences.OnSharedPreferenceChangeListener {
override val kodein by closestKodein()
private val viewModelFactory: CurrentWeatherViewModelFactory by instance()
private lateinit var viewModel: CurrentWeatherViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.current_weather_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
loadBannerAd()
viewModel = ViewModelProvider(this, viewModelFactory)
.get(CurrentWeatherViewModel::class.java)
bindUI()
applyDarkModeSetting()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
private fun loadBannerAd() {
MobileAds.initialize(context) {}
mAdViewCurrent = adView
val adRequest = AdRequest.Builder().build()
mAdViewCurrent.loadAd(adRequest)
mAdViewCurrent.adListener = object: AdListener() {
override fun onAdLoaded() {
}
override fun onAdFailedToLoad(adError : LoadAdError) {
// Code to be executed when an ad request fails.
}
override fun onAdOpened() {
// Code to be executed when an ad opens an overlay that
// covers the screen.
}
override fun onAdClicked() {
// Code to be executed when the user clicks on an ad.
}
override fun onAdClosed() {
}
}
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == "GENDER") {
val prefs = sharedPreferences?.getString(key, "1")
when(prefs?.toInt()) {
1->{
bindUI()
}
2->{
bindUIWomen()
}
}
}
applyDarkModeSetting()
}
override fun onDestroy() {
super.onDestroy()
context?.let {
PreferenceManager.getDefaultSharedPreferences(it)
.registerOnSharedPreferenceChangeListener(this)
}
}
private fun applyDarkModeSetting() {
val sharedPreferences = context?.let { PreferenceManager.getDefaultSharedPreferences(it) }
val settingValue = sharedPreferences?.getString("GENDER", null)?.toIntOrNull() ?: 1
val mode = when (settingValue) {
1 -> {bindUI()}
2 -> {bindUIWomen()}
else -> Toast.makeText(context, "Nothing", Toast.LENGTH_LONG).show()
}
}
private fun bindUI() = launch(Dispatchers.Main) {
val currentWeather = viewModel.weather.await()
val weatherLocation = viewModel.weatherLocation.await()
val CurrentWeatherEntries = viewModel.weather.await()
CurrentWeatherEntries.observe(viewLifecycleOwner, Observer { weatherAll ->
if (weatherAll == null){
return#Observer
}
recyclerViewClothes.apply {
layoutManager = LinearLayoutManager(activity)
adapter = ClothesAdapter(weatherAll)
}
})
weatherLocation.observe(viewLifecycleOwner, Observer { location ->
if (location == null) return#Observer
updateLocation(location.name)
updateCityName(location.name)
})
currentWeather.observe(viewLifecycleOwner, Observer {
if(it == null) return#Observer
group_loading.visibility = View.GONE
weatherAll.visibility = View.VISIBLE
updateDateToday()
//updateIsDay(it.isDay)
updateHumidity(it.humidity)
updateTemperature(it.temperature, it.feelsLikeTemperature)
updateCondition(it.conditionText)
updatePressure(it.pressure)
updateWind(it.windSpeed)
updateCloudy(it.cloud)
GlideApp.with(this#CurrentWeatherFragment)
.load("https:${it.conditionIconUrl}")
.into(imageView_condition_icon)
})
refreshApp()
}
private fun List<UnitSpecificCurrentWeatherEntry>.toCurrentWeatherItems() : List<ClothesAdapter> {
return this.map {
ClothesAdapter(it) //ClothesAdapter
}
}
// private fun List<UnitSpecificCurrentWeatherEntry>.toCurrentWeatherItems() : List<ClothesAdapterWomen> {
// return this.map {
// ClothesAdapterWomen(it) //ClothesAdapter
// }
// }
private fun chooseLocalizationUnitAbbreviation(metric: String, imperial: String): String {
return if(viewModel.isMetricUnit) metric else imperial
}
private fun updateLocation(location: String) {
(activity as? AppCompatActivity)?.supportActionBar?.subtitle = getString(R.string.todayTitle)
}
private fun updateDateToday() {
(activity as? AppCompatActivity)?.supportActionBar?.subtitle = getString(R.string.todayTitle)
}
private fun updateCityName(cityName: String) {
textView_cityName.text = cityName
}
}
I need to create a Flutter plugin and link native Android code from ADA bot. I have no experience with android but by following the documentation, it should be simple:
val adaView = AdaEmbedView(getContext())
val adaSettings = AdaEmbedView.Settings.Builder("ada-example").build()
adaView.initialize(adaSettings)
val dialog = AdaEmbedDialog()
adaDialog.arguments = Bundle().apply {
putParcelable(AdaEmbedDialog.ARGUMENT_SETTINGS, settings)
}
dialog.show(supportFragmentManager, AdaEmbedDialog.TAG)
When I run this code I get the error:
Failed to handle method call
java.lang.IllegalStateException: FragmentManager has not been attached to a host.
I write the code in the flutter create plugin autogenerated file FlutterAdaPlugin.kt and it looks like this:
public class FlutterAdaPlugin: FlutterPlugin, ActivityAware, MethodCallHandler, PluginRegistry.ActivityResultListener, FragmentActivity() {
private lateinit var methodChannel : MethodChannel
private lateinit var eventChannel : EventChannel
private lateinit var eventSink : EventChannel.EventSink
private lateinit var adaFramerwork: AdaEmbedView
private lateinit var applicationCnt: Context
private var activity: Activity? = null
private lateinit var addaSettings: AdaEmbedView.Settings
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) : Boolean {
super.onActivityResult(requestCode, resultCode, data)
return true
}
override fun onAttachedToEngine(#NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
onAttachedToEngine(flutterPluginBinding.applicationContext, flutterPluginBinding.binaryMessenger)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.applicationCnt = applicationContext
}
private fun onAttachedToEngine(applicationContext: Context, messenger: BinaryMessenger) {
this.applicationCnt = applicationContext
methodChannel = MethodChannel(messenger, "FlutterAdaPlugin")
eventChannel = EventChannel(messenger, "FlutterAdaPluginStream")
methodChannel.setMethodCallHandler(this)
eventChannel.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(listener: Any?, eventSink: EventChannel.EventSink) {
this#FlutterAdaPlugin.eventSink = eventSink;
}
})
}
companion object {
#JvmStatic
fun registerWith(registrar: Registrar) {
val plugin = FlutterAdaPlugin()
plugin.activity = registrar.activity()
registrar.addActivityResultListener(plugin)
plugin.onAttachedToEngine(registrar.context(), registrar.messenger())
}
}
override fun onMethodCall(#NonNull call: MethodCall, #NonNull result: Result) {
when (call.method) {
"launchNavWebSupport" -> launchNavWebSupport()
}
else -> result.notImplemented()
}
}
private fun launchNavWebSupport() {
setupAdaFramework()
val dialog = AdaEmbedDialog()
dialog.arguments = Bundle().apply {
putParcelable(AdaEmbedDialog.ARGUMENT_SETTINGS, addaSettings)
}
if(supportFragmentManager != null) {
}
dialog.show(supportFragmentManager, AdaEmbedDialog.TAG)
}
private fun setupAdaFramework() {
val activity: Context = activity?.takeIf { activity != null } ?: return
adaFramerwork = AdaEmbedView(activity)
addaSettings = AdaEmbedView.Settings.Builder("test")
.build()
adaFramerwork.initialize(addaSettings)
}
override fun onDetachedFromEngine(#NonNull binding: FlutterPlugin.FlutterPluginBinding) {
eventChannel.setStreamHandler(null)
methodChannel.setMethodCallHandler(null)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
binding.addActivityResultListener(this)
}
override fun onDetachedFromActivity() {
activity = null
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activity = binding.activity
binding.addActivityResultListener(this)
}
override fun onDetachedFromActivityForConfigChanges() {
activity = null
}
}
I have two screens first one has recycler view list of data and searchView above it that's filter data in this recycler, the view Model code of the first fragment
class MscInspectionViewModel(val activity: LaunchActivity, val mRootView: MscInspectFragment) :
BaseViewModel(),
SwipeRefreshLayout.OnRefreshListener {
val toolBarTitle: MutableLiveData<String> = MutableLiveData()
private val getDataError = MutableLiveData<Boolean>()
var listType = MutableLiveData<Int>()
val hint = MutableLiveData<String>()
private var isRefreshing: Boolean = false
private var mSharedPreferences: SharedPreferences? = null
val dataListAdapter = ContainersUnderCheckAdapter(activity)
val backClickListener = View.OnClickListener { activity.supportFragmentManager.popBackStack() }
val filterDataByTab = object : TabLayout.OnTabSelectedListener {
override fun onTabReselected(tab: TabLayout.Tab?) {
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabSelected(tab: TabLayout.Tab?) {
when (tab!!.text) {
activity.resources.getString(R.string.cidPending) -> {
listType.value = 0
getPendingData()
}
activity.resources.getString(R.string.cidDone) -> {
listType.value = 1
getDoneData()
}
}
}
}
val filterData = object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
if (query.length > 2) {
val mQuery = Utility(activity).switchArabicNumerals(query)
dataListAdapter.getFilter(3, listType.value!!).filter(mQuery)
} else {
errorMessage.value = activity.resources.getString(R.string.addCorrectNumber)
}
return true
}
override fun onQueryTextChange(newText: String): Boolean {
if (newText.length > 2) {
val mQuery = Utility(activity).switchArabicNumerals(newText)
dataListAdapter.getFilter(3, listType.value!!).filter(mQuery)
}
return false;
}
}
val closeImgListener = View.OnClickListener {
mRootView.svSearchMSC.setQuery("", true)
if (listType.value == 1) {
dataListAdapter.getFilter(1, listType.value!!).filter("ANY")
} else if (listType.value == 0) {
dataListAdapter.getFilter(2, listType.value!!).filter("PENDING")
}
}
init {
listType.value = 0
mSharedPreferences = getDefaultSharedPreferences(activity.applicationContext)
toolBarTitle.value = activity.resources.getString(R.string.mscInspectTitle)
hint.value = activity.resources.getString(R.string.msc_search)
getData()
}
fun getData() {
onRetrievePostListStart()
subscription = apiAccount.getContainersUnderCheck(
"getContainersUnderCheck",
mSharedPreferences!!.getString(Constants.CFID, "")!!,
mSharedPreferences!!.getString(Constants.CFTOKEN, "")!!
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {}
.doOnTerminate {}
.subscribe({ result ->
result?.let {
if (result.ResponseCode != null && result.ResponseCode.trim() != "000") {
onRetrievePostListError(result.ResponseMessage)
} else {
result.ContainersData?.let { it1 -> onRetrievePostListSuccess(it1) }
}
}
}, { throwable ->
android.util.Log.e("getDataInquiry", throwable.message!!)
onRetrievePostListError(activity.resources.getString(R.string.general_error))
})
}
private fun getPendingData() {
val query = mRootView.svSearchMSC.query.toString()
if (query == "") {
dataListAdapter.getFilter(2, listType.value!!).filter("PENDING")
} else {
if (query.length > 2) {
dataListAdapter.getFilter(3, listType.value!!).filter(query)
} else {
errorMessage.value = activity.resources.getString(R.string.addCorrectNumber)
}
}
}
private fun getDoneData() {
val query = mRootView.svSearchMSC.query.toString()
if (query == "") {
dataListAdapter.getFilter(1, listType.value!!).filter("ANY")
} else {
if (query.length > 2) {
dataListAdapter.getFilter(3, listType.value!!).filter(query)
} else {
errorMessage.value = activity.resources.getString(R.string.addCorrectNumber)
}
}
}
private fun onRetrievePostListStart() {
loading.value = true
}
private fun onRetrievePostListFinish() {
loading.value = false
isRefreshing = false
}
private fun onRetrievePostListSuccess(containersData: List<ContainersData>) {
onRetrievePostListFinish()
dataListAdapter.updateInquiryAdapter(containersData as ArrayList<ContainersData>)
if (listType.value == 1) {
dataListAdapter.getFilter(1, listType.value!!).filter("ANY")
} else if (listType.value == 0) {
dataListAdapter.getFilter(2, listType.value!!).filter("PENDING")
}
}
private fun onRetrievePostListError(message: String?) {
onRetrievePostListFinish()
getDataError.value = true
errorMessage.value = message
}
override fun onCleared() {
super.onCleared()
subscription.dispose()
}
override fun onRefresh() {
isRefreshing = true
getData()
}
}
adapter is :
class ContainersUnderCheckAdapter(val activity: LaunchActivity) :
RecyclerView.Adapter<ContainersUnderCheckAdapter.ViewHolder>() {
private lateinit var mDataSet: ArrayList<ContainersData>
private lateinit var mDataSetFiltered: ArrayList<ContainersData>
fun updateInquiryAdapter(dataSet: ArrayList<ContainersData>) {
mDataSet = ArrayList()
mDataSet.clear()
mDataSet.addAll(dataSet)
mDataSetFiltered = mDataSet
getFilter(2, 1).filter("PENDING")
// notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ContianerItemFieldLayoutBinding = DataBindingUtil
.inflate(
LayoutInflater.from(parent.context),
R.layout.contianer_item_field_layout,
parent,
false
)
return ViewHolder(binding, activity)
}
override fun getItemCount(): Int {
return if (::mDataSetFiltered.isInitialized) mDataSetFiltered.size else 0
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(mDataSetFiltered[position])
}
operator fun get(position: Int): ContainersData {
return mDataSetFiltered.get(position)
}
/**
* #filterType :
* IF 1 : filter on Data Type RJCTD + APPROVED
* 2 : filter on Data Type PENDING
* 3 :
*/
fun getFilter(filterType: Int, listType: Int): Filter {
return object : Filter() {
override fun performFiltering(charSequence: CharSequence): FilterResults {
val charString = charSequence.toString()
mDataSetFiltered = if (charString.isEmpty()) {
mDataSet
} else {
val filteredList = ArrayList<ContainersData>()
for (row in mDataSet) {
when (filterType) {
1 -> {
if (row.status == "RJCTD" || row.status == "APPROVED") {
filteredList.add(row)
}
}
2 -> {
if (row.status == charString) {
filteredList.add(row)
}
}
3 -> {
when (listType) {
0 -> {
if ((row.CID!!.contains(charString.toUpperCase(Locale.ROOT)) || row.TN!!.contains(
charSequence
) || row.PN!!.contains(charSequence)) && row.status == "PENDING"
) {
filteredList.add(row)
}
}
1 -> {
if ((row.CID!!.contains(charString.toUpperCase(Locale.ROOT)) || row.TN!!.contains(
charSequence
) || row.PN!!.contains(charSequence)) && row.status != "PENDING"
) {
filteredList.add(row)
}
}
}
}
}
}
filteredList
}
val filterResults = FilterResults()
filterResults.values = mDataSetFiltered
return filterResults
}
override fun publishResults(
charSequence: CharSequence,
filterResults: FilterResults
) {
if (::mDataSetFiltered.isInitialized) {
mDataSetFiltered = try {
filterResults.values as ArrayList<ContainersData>
} catch (e: Exception) {
Log.e("mDataSetFiltered",e.message!!)
ArrayList()
}
when (filterType) {
1->{
mDataSetFiltered.sortWith(Comparator { p0, p1 -> p1!!.UpdateDate.compareTo(p0!!.UpdateDate) })
}
2->{
mDataSetFiltered.sortWith(Comparator { p0, p1 -> p0!!.ID!!.compareTo(p1.ID!!) })
}
}
}
// refresh the list with filtered data
notifyDataSetChanged()
}
}
}
class ViewHolder(
private val binding: ContianerItemFieldLayoutBinding,
val activity: LaunchActivity
) : RecyclerView.ViewHolder(binding.root) {
private val viewModel = MscInspectionListViewModel(activity)
fun bind(data: ContainersData) {
viewModel.bind(data)
binding.viewModel = viewModel
}
}
}
any data in this recycler on click go to fragment has tow recycler first one to show data, the second one to pick Images
the second-page code
class MSCDataFragment : Fragment() {
lateinit var rootView: View
lateinit var activity: LaunchActivity
lateinit var utility: Utility
lateinit var loadingView: LoadingView
private lateinit var viewModel: MSCDataViewModel
private lateinit var binding: FragmentMscdataBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (getActivity() != null) {
activity = getActivity() as LaunchActivity
utility = Utility(activity)
loadingView = LoadingView(activity)
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_mscdata, container, false)
rootView = binding.root
initial()
return rootView
}
private fun initial() {
viewModel = ViewModelProvider(
this, ViewModelFactory(
activity,
arguments!!.getSerializable("Data") as ContainersData
)
).get(MSCDataViewModel::class.java)
binding.viewModel = viewModel
// binding.imgList.layoutManager = GridLayoutManager(activity, 3)
binding.containerInfo.layoutManager = LinearLayoutManager(activity)
binding.openCIDNotValid.typeface =
Typeface.createFromAsset(activity.assets, "Bahij_Janna-Regular.ttf")
binding.openCIDNotValid.setOnCheckedChangeListener(viewModel.onOpenCidNotValidListener)
viewModel.loading.observe(this, Observer { loading ->
loading?.let {
if (it) {
loadingView.show()
} else {
loadingView.dismiss()
}
}
})
viewModel.errorMessage.observe(this, Observer { msg ->
msg?.let {
utility.ShowToast(msg)
}
})
viewModel.imagesAdapters2.observe(this, Observer { msg ->
msg?.let {
binding.imgList.apply {
layoutManager = GridLayoutManager(activity, 3)
adapter = it
}
}
})
rootView.toolbar_Back.setOnClickListener(viewModel.backClickListener)
binding.btnAddImages.setOnClickListener(viewModel.pickImages)
binding.successContianer.setOnClickListener(viewModel.correctContainer)
binding.damagedContianer.setOnClickListener(viewModel.wrongContainer)
}
}
the view model is :
class MSCDataViewModel(val activity: LaunchActivity, val containersData: ContainersData) :
BaseViewModel(), GetImagesListener {
#Inject
lateinit var restApiAccount: RestApiAccount
val toolBarTitle: MutableLiveData<String> = MutableLiveData()
val ButtonText: MutableLiveData<String> = MutableLiveData()
var openCIDNotValidVisibility = MutableLiveData<Int>()
private val getDataError = MutableLiveData<Boolean>()
val btnImagesVisibility = MutableLiveData<Int>()
var imgeNoteVisibility = MutableLiveData<Int>()
var successVisibility = MutableLiveData<Int>()
var damagedVisibility = MutableLiveData<Int>()
var notesVisibility = MutableLiveData<Int>()
val btnVisibility = MutableLiveData<Int>()
var canNotOpen = MutableLiveData<Int>()
private val images = ArrayList<Image>()
var utility = Utility(activity)
private var CURRENTINDEX = 0
private var mSharedPreferences: SharedPreferences? = null
val DataListAdapter = ContainerDataAdapter(activity)
var imagesAdapter = ContainerImagesAdapter(activity, containersData.status!!, ArrayList())
val imagesAdapters2 = MutableLiveData<ContainerImagesAdapter2>()
val userInfo: UserInfo
val backClickListener = View.OnClickListener { activity.supportFragmentManager.popBackStack() }
val pickImages = View.OnClickListener {
pickImages()
}
val correctContainer = View.OnClickListener {}
val onOpenCidNotValidListener =
CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
if (isChecked) {
successVisibility.value = View.GONE
canNotOpen.value = 1
} else {
canNotOpen.value = 0
successVisibility.value = View.VISIBLE
}
}
val wrongContainer = View.OnClickListener {}
var mscNotes: ObservableField<String> = ObservableField("")
init {
canNotOpen.value = 0
mSharedPreferences =
PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
toolBarTitle.value = containersData.CID
ButtonText.value = activity.resources.getString(R.string.cleanContianer)
userInfo = utility.readObjectFromSharedPreferences(
mSharedPreferences,
Constants.USER_INFO_KEY,
UserInfo::class.java
) as UserInfo
openCIDNotValidVisibility.value = View.GONE
fillData()
}
private fun fillData() {
val data: LinkedHashMap<String, String> = containersData.data!!
val captionsMap = utility.readObjectFromSharedPreferences(
mSharedPreferences, Constants.CAPTIONS_MAP_KEY,
HashMap::class.java
) as HashMap<String, String>
if (containersData.data.size > 0) {
val list = ArrayList<KeyValueModel>()
for (inside in data.keys) {
val ky = captionsMap[inside]
val value = data[inside].toString()
ky?.let { KeyValueModel(it, value) }?.let { list.add(it) }
}
DataListAdapter.updateInquiryAdapter(list)
} else {
errorMessage.value = activity.resources.getString(R.string.no_data)
}
if (containersData.ImageList != null && containersData.ImageList.isNotEmpty()) {
imagesAdapter.updateContainerImagesAdapter(containersData.ImageList)
}
}
private fun pickImages() {
activity.setCallBack(this)
val pictureDialog: AlertDialog
val builder = activity.let { AlertDialog.Builder(it) }
val dialogView = View.inflate(activity, R.layout.choose_camera_method, null)
builder.setView(dialogView)
val nafithPopupContainer = dialogView.findViewById<RelativeLayout>(R.id.RLTitle)
nafithPopupContainer.setBackgroundColor(
ContextCompat.getColor(
activity,
R.color.mainColor
)
)
val popUpGallery = dialogView.findViewById<LinearLayout>(R.id.PopupGellary)
val popUpCamera = dialogView.findViewById<LinearLayout>(R.id.PopupCamera)
pictureDialog = builder.create()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Objects.requireNonNull<Window>(pictureDialog.window)
.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
} else {
if (pictureDialog.window != null) {
pictureDialog.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
}
}
popUpGallery.setOnClickListener {
fromGallery()
pictureDialog.dismiss()
}
popUpCamera.setOnClickListener {
fromCamera()
pictureDialog.dismiss()
}
val popupClose = dialogView.findViewById<ImageView>(R.id.popupClose)
popupClose.setOnClickListener { pictureDialog.dismiss() }
pictureDialog.show()
}
private fun fromGallery() {
ImagePicker.create(activity)
.toolbarImageTitle(activity.resources.getString(R.string.get_image))
.toolbarArrowColor(ContextCompat.getColor(activity, R.color.colorWhite))
.showCamera(false)
.limit(6)
.start()
}
private fun fromCamera() {
ImagePicker.cameraOnly().start(activity)
}
override fun onGetImage(image: Image) {
imgeNoteVisibility.value = View.GONE
imagesAdapter.updateContainerImagesAdapter(image)
images.add(image)
}
override fun addingImagesDone(mImages: MutableList<Image>) {
images.clear()
images.addAll(mImages)
imgeNoteVisibility.value = View.GONE
val listString :ArrayList<String> = ArrayList()
for (i in mImages.indices){
listString.add(mImages[i].path)
}
imagesAdapters2.value = ContainerImagesAdapter2(activity,containersData.status!!,listString)
imagesAdapters2.value!!.notifyItemRangeChanged(0,listString.size)
}
override fun onImgDelete(image: String) {
var x = 0
try {
for (i in 0 until images.size) {
x = i
if (images[i].path == image) {
images.remove(images[i])
}
}
} catch (e: Exception) {
Log.e("errorImages", e.message!!)
Log.e("xx", x.toString())
}
}
private fun onRetrievePostListStart() {
loading.value = true
}
private fun onRetrievePostListFinish() {
loading.value = false
}
private fun onRetrievePostListSuccess(msg: String?) {
onRetrievePostListFinish()
}
private fun onRetrievePostListError(message: String?) {
onRetrievePostListFinish()
getDataError.value = true
errorMessage.value = message
}
}
Adapter code is :
class ContainerImagesAdapter2() : RecyclerView.Adapter<ContainerImagesAdapter2.ViewHolder>() {
var status: String = ""
lateinit var activity: LaunchActivity
lateinit var utility: Utility
constructor(
mActivity: LaunchActivity,
mStatus: String,
pathsList: ArrayList<String>
) : this() {
activity = mActivity
pathsDataSet = pathsList
status = mStatus
utility = Utility(activity)
}
private var pathsDataSet: ArrayList<String> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ContianerImageFieldBinding = DataBindingUtil
.inflate(
LayoutInflater.from(parent.context),
R.layout.contianer_image_field,
parent,
false
)
return ViewHolder(binding, activity)
}
override fun getItemCount(): Int {
return pathsDataSet.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindPath(pathsDataSet[position], position)
}
inner class ViewHolder(
private val binding: ContianerImageFieldBinding,
val activity: LaunchActivity
) : RecyclerView.ViewHolder(binding.root) {
private val viewModel = MscImagesListViewModel(activity)
fun bindPath(data: String, position: Int) {
viewModel.bindPath(data)
binding.viewModel = viewModel
if (status != "PENDING") {
binding.closeImg.visibility = View.GONE
}
binding.closeImg.setOnClickListener {}
binding.mainImg.setOnClickListener {
val fragment = FullImageFragment()
val bundle = Bundle()
val list = ArrayList<String>()
for (item in 0 until pathsDataSet.size) {
list.add(pathsDataSet[item])
}
bundle.putSerializable("ImageList", list)
bundle.putInt("Position", position)
fragment.arguments = bundle
activity.supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment).addToBackStack(fragment.tag)
.commit()
}
}
}
}
if you filter data using search view in the first-page and pick images in the second page , list of picked images doesn't appear, if you going to the second page without filtering data everything ok
solve Problem found
Just Update constraint-layout library in gradle dependencies to version '2.0.0-beta4'