问:
ViewModel updates on screen rotation
I've created a simple project to study Kotlin and Android architecture

https://github.com/AOreshin/shtatus

The screen consists of RecyclerView and three EditTexts.

Corresponding ViewModel is exposing 7 LiveData's:

Three LiveData corresponding to filters

Event to notify the user that no entries are found

Event to notify the user that no entries are present

Status of SwipeRefreshLayout

List of connections to show based on filter input

When user types text in filter ViewModel's LiveData gets notified about the changes and updates the data. I 've read that it's a bad practice to expose MutableLiveData to Activities/Fragments but they have to notify ViewModel about the changes somehow. When no entries are found based on the user's input Toast is shown.

The problem

When the user enters filter values that have no matches, Toast is shown. If the user then rotates the device Toast is shown again and again.

I've read these articles:

https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150

https://proandroiddev.com/livedata-with-single-events-2395dea972a8

But I don't understand how I can apply these to my use case. I think the problem in how I perform the updates

```<code>private val connections = connectionRepository.allConnections()
private val mediatorConnection = MediatorLiveData<List<Connection>>().also {
    it.value = connections.value
}

private val refreshLiveData = MutableLiveData(RefreshStatus.READY)
private val noMatchesEvent = SingleLiveEvent<Void>()
private val emptyTableEvent = SingleLiveEvent<Void>()

val nameLiveData = MutableLiveData<String>()
val urlLiveData = MutableLiveData<String>()
val actualStatusLiveData = MutableLiveData<String>()

init {
    with(mediatorConnection) {
        addSource(connections) { update() }
        addSource(nameLiveData) { update() }
        addSource(urlLiveData) { update() }
        addSource(actualStatusLiveData) { update() }
    }
}

fun getRefreshLiveData(): LiveData<RefreshStatus> = refreshLiveData
fun getNoMatchesEvent(): LiveData<Void> = noMatchesEvent
fun getEmptyTableEvent(): LiveData<Void> = emptyTableEvent
fun getConnections(): LiveData<List<Connection>> = mediatorConnection

private fun update() {
    if (connections.value.isNullOrEmpty()) {
        emptyTableEvent.call()
    } else {
        mediatorConnection.value = connections.value?.filter { connection -> getPredicate().test(connection) }

        if (mediatorConnection.value.isNullOrEmpty()) {
            noMatchesEvent.call()
        }
    }
}
</code>```

update() gets triggered on screen rotation because of new subscription to mediatorConnection and MediatorLiveData.onActive() is called. And it's intented behavior

Android live data - observe always fires after config change

Code for showing toast

```<code>package com.github.aoreshin.shtatus.fragments

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.Toast
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.github.aoreshin.shtatus.R
import com.github.aoreshin.shtatus.ShatusApplication
import com.github.aoreshin.shtatus.viewmodels.ConnectionListViewModel
import javax.inject.Inject

class ConnectionListFragment : Fragment() {
    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory

    private lateinit var refreshLayout: SwipeRefreshLayout

    private lateinit var nameEt: EditText
    private lateinit var urlEt: EditText
    private lateinit var statusCodeEt: EditText

    private lateinit var viewModel: ConnectionListViewModel

    private lateinit var recyclerView: RecyclerView
    private lateinit var viewAdapter: ConnectionListAdapter

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_connection_list, container, false)

        val application = (requireActivity().application as ShatusApplication)
        application.appComponent.inject(this)

        val viewModelProvider = ViewModelProvider(this, viewModelFactory)
        viewModel = viewModelProvider.get(ConnectionListViewModel::class.java)

        bindViews(view)
        setupObservers()
        setupListeners()
        addFilterValues()
        setupRecyclerView()
        return view
    }

    private fun setupObservers() {
        with(viewModel) {
            getConnections().observe(viewLifecycleOwner, Observer { viewAdapter.submitList(it) })

            getRefreshLiveData().observe(viewLifecycleOwner, Observer { status ->
                when (status) {
                    ConnectionListViewModel.RefreshStatus.LOADING -> refreshLayout.isRefreshing = true
                    ConnectionListViewModel.RefreshStatus.READY -> refreshLayout.isRefreshing = false
                    else -> throwException(status.toString())
                }
            })

            getNoMatchesEvent().observe(viewLifecycleOwner, Observer { showToast(R.string.status_no_matches) })
            getEmptyTableEvent().observe(viewLifecycleOwner, Observer { showToast(R.string.status_no_connections) })
        }
    }

    private fun setupRecyclerView() {
        viewAdapter = ConnectionListAdapter(parentFragmentManager, ConnectionItemCallback())
        recyclerView.apply {
            layoutManager = LinearLayoutManager(context)
            adapter = viewAdapter
        }
    }

    private fun addFilterValues() {
        with(viewModel) {
            nameEt.setText(nameLiveData.value)
            urlEt.setText(urlLiveData.value)
            statusCodeEt.setText(actualStatusLiveData.value)
        }
    }

    private fun bindViews(view: View) {
        with(view) {
            recyclerView = findViewById(R.id.recycler_view)
            refreshLayout = findViewById(R.id.refresher)
            nameEt = findViewById(R.id.nameEt)
            urlEt = findViewById(R.id.urlEt)
            statusCodeEt = findViewById(R.id.statusCodeEt)
        }
    }

    private fun setupListeners() {
        with(viewModel) {
            refreshLayout.setOnRefreshListener { send() }
            nameEt.addTextChangedListener { nameLiveData.value = it.toString() }
            urlEt.addTextChangedListener { urlLiveData.value = it.toString() }
            statusCodeEt.addTextChangedListener { actualStatusLiveData.value = it.toString() }
        }
    }

    private fun throwException(status: String) {
        throw IllegalStateException(getString(R.string.error_no_such_status) + status)
    }

    private fun showToast(resourceId: Int) {
        Toast.makeText(context, getString(resourceId), Toast.LENGTH_SHORT).show()
    }

    override fun onDestroyView() {
        super.onDestroyView()
        with(viewModel) {
            getNoMatchesEvent().removeObservers(viewLifecycleOwner)
            getRefreshLiveData().removeObservers(viewLifecycleOwner)
            getEmptyTableEvent().removeObservers(viewLifecycleOwner)
            getConnections().removeObservers(viewLifecycleOwner)
        }
    }
}
</code>```

How I should address this issue?

答:
After some head scratching I've decided to go with internal ViewModel statuses, this way logic in Activity/Fragment is kept to a minimum.

So now my ViewModel looks like this:

```<code>package com.github.aoreshin.shtatus.viewmodels

import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.github.aoreshin.shtatus.events.SingleLiveEvent
import com.github.aoreshin.shtatus.room.Connection
import io.reactivex.FlowableSubscriber
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import io.reactivex.subscribers.DisposableSubscriber
import okhttp3.ResponseBody
import retrofit2.Response
import java.util.function.Predicate
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class ConnectionListViewModel @Inject constructor(
    private val connectionRepository: ConnectionRepository
) : ViewModel() {
    private var tableStatus = TableStatus.OK

    private val connections = connectionRepository.allConnections()
    private val mediatorConnection = MediatorLiveData<List<Connection>>()

    private val stopRefreshingEvent = SingleLiveEvent<Void>()
    private val noMatchesEvent = SingleLiveEvent<Void>()
    private val emptyTableEvent = SingleLiveEvent<Void>()

    private val nameLiveData = MutableLiveData<String>()
    private val urlLiveData = MutableLiveData<String>()
    private val statusLiveData = MutableLiveData<String>()

    init {
        with(mediatorConnection) {
            addSource(connections) { update() }
            addSource(nameLiveData) { update() }
            addSource(urlLiveData) { update() }
            addSource(statusLiveData) { update() }
        }
    }

    fun getStopRefreshingEvent(): LiveData<Void> = stopRefreshingEvent
    fun getNoMatchesEvent(): LiveData<Void> = noMatchesEvent
    fun getEmptyTableEvent(): LiveData<Void> = emptyTableEvent
    fun getConnections(): LiveData<List<Connection>> = mediatorConnection
    fun getName(): String? = nameLiveData.value
    fun getUrl(): String? = urlLiveData.value
    fun getStatus(): String? = statusLiveData.value
    fun setName(name: String) { nameLiveData.value = name }
    fun setUrl(url: String) { urlLiveData.value = url }
    fun setStatus(status: String) { statusLiveData.value = status }

    private fun update() {
        if (connections.value != null) {
            if (connections.value.isNullOrEmpty()) {
                if (tableStatus != TableStatus.EMPTY) {
                    emptyTableEvent.call()
                    tableStatus = TableStatus.EMPTY
                }
            } else {
                mediatorConnection.value = connections.value?.filter { connection -> getPredicate().test(connection) }

                if (mediatorConnection.value.isNullOrEmpty()) {
                    if (tableStatus != TableStatus.NO_MATCHES) {
                        noMatchesEvent.call()
                        tableStatus = TableStatus.NO_MATCHES
                    }
                } else {
                    tableStatus = TableStatus.OK
                }
            }
        }
    }

    fun send() {
        if (!connections.value.isNullOrEmpty()) {
            val singles = connections.value?.map { connection ->
                val id = connection.id
                val description = connection.description
                val url = connection.url
                var message = ""

                connectionRepository.sendRequest(url)
                    .doOnSuccess { message = it.code().toString() }
                    .doOnError { message = it.message!! }
                    .doFinally {
                        val result = Connection(id, description, url, message)
                        connectionRepository.insert(result)
                    }
            }

            Single.mergeDelayError(singles)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .doFinally { stopRefreshingEvent.call() }
                .subscribe(getSubscriber())
        } else {
            stopRefreshingEvent.call()
        }
    }

    private fun getSubscriber() : FlowableSubscriber<Response<ResponseBody>> {
        return object: DisposableSubscriber<Response<ResponseBody>>() {
            override fun onComplete() { Log.d(TAG, "All requests sent") }
            override fun onNext(t: Response<ResponseBody>?) { Log.d(TAG, "Request is done") }
            override fun onError(t: Throwable?) { Log.d(TAG, t!!.message!!) }
        }
    }

    private fun getPredicate(): Predicate<Connection> {
        return Predicate { connection ->
            connection.description.contains(nameLiveData.value.toString(), ignoreCase = true)
                    && connection.url.contains(urlLiveData.value.toString(), ignoreCase = true)
                    && connection.actualStatusCode.contains(
                statusLiveData.value.toString(),
                ignoreCase = true
            )
        }
    }

    private enum class TableStatus {
        NO_MATCHES,
        EMPTY,
        OK
    }

    companion object {
        private const val TAG = "ConnectionListViewModel"
    }
}
</code>```

And corresponding Fragment looks like this:

```<code>package com.github.aoreshin.shtatus.fragments

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.Toast
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.github.aoreshin.shtatus.R
import com.github.aoreshin.shtatus.ShatusApplication
import com.github.aoreshin.shtatus.viewmodels.ConnectionListViewModel
import javax.inject.Inject

class ConnectionListFragment : Fragment() {
    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory

    private lateinit var refreshLayout: SwipeRefreshLayout

    private lateinit var nameEt: EditText
    private lateinit var urlEt: EditText
    private lateinit var statusCodeEt: EditText

    private lateinit var viewModel: ConnectionListViewModel

    private lateinit var recyclerView: RecyclerView
    private lateinit var viewAdapter: ConnectionListAdapter

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_connection_list, container, false)

        val application = (requireActivity().application as ShatusApplication)
        application.appComponent.inject(this)

        val viewModelProvider = ViewModelProvider(this, viewModelFactory)
        viewModel = viewModelProvider.get(ConnectionListViewModel::class.java)

        bindViews(view)
        setupObservers()
        setupListeners()
        addFilterValues()
        setupRecyclerView()
        return view
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        if (savedInstanceState != null) {
            refreshLayout.isRefreshing = savedInstanceState.getBoolean(REFRESHING, false)
        }
    }

    private fun setupObservers() {
        with(viewModel) {
            getConnections().observe(viewLifecycleOwner, Observer { viewAdapter.submitList(it) })
            getStopRefreshingEvent().observe(viewLifecycleOwner, Observer { refreshLayout.isRefreshing = false })
            getNoMatchesEvent().observe(viewLifecycleOwner, Observer { showToast(R.string.status_no_matches) })
            getEmptyTableEvent().observe(viewLifecycleOwner, Observer { showToast(R.string.status_no_connections) })
        }
    }

    private fun setupRecyclerView() {
        viewAdapter = ConnectionListAdapter(parentFragmentManager, ConnectionItemCallback())
        recyclerView.apply {
            layoutManager = LinearLayoutManager(context)
            adapter = viewAdapter
        }
    }

    private fun addFilterValues() {
        with(viewModel) {
            nameEt.setText(getName())
            urlEt.setText(getUrl())
            statusCodeEt.setText(getStatus())
        }
    }

    private fun bindViews(view: View) {
        with(view) {
            recyclerView = findViewById(R.id.recycler_view)
            refreshLayout = findViewById(R.id.refresher)
            nameEt = findViewById(R.id.nameEt)
            urlEt = findViewById(R.id.urlEt)
            statusCodeEt = findViewById(R.id.statusCodeEt)
        }
    }

    private fun setupListeners() {
        with(viewModel) {
            refreshLayout.setOnRefreshListener { send() }
            nameEt.addTextChangedListener { setName(it.toString()) }
            urlEt.addTextChangedListener { setUrl(it.toString()) }
            statusCodeEt.addTextChangedListener { setStatus(it.toString()) }
        }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putBoolean(REFRESHING, refreshLayout.isRefreshing)
    }

    private fun showToast(resourceId: Int) {
        Toast.makeText(context, getString(resourceId), Toast.LENGTH_SHORT).show()
    }

    override fun onDestroyView() {
        super.onDestroyView()
        with(viewModel) {
            getNoMatchesEvent().removeObservers(viewLifecycleOwner)
            getEmptyTableEvent().removeObservers(viewLifecycleOwner)
            getStopRefreshingEvent().removeObservers(viewLifecycleOwner)
            getConnections().removeObservers(viewLifecycleOwner)
        }
    }

    companion object {
        private const val REFRESHING = "isRefreshing"
    }
}
</code>```

Pros

No additional dependencies

Usage of widespread SingleLiveEvent

Pretty straightforward to implement

Cons

Conditional logic is quickly getting out of hand even in this simple case, surely needs refactoring. Not sure if this approach will work in real-life complex scenarios.

If there are cleaner and more concise approaches to solve this problem I will be happy to hear about them!

Title: ViewModel updates on screen rotation

Body:
I've created a simple project to study Kotlin and Android architecture

https://github.com/AOreshin/shtatus

The screen consists of RecyclerView and three EditTexts.

Corresponding ViewModel is exposing 7 LiveData's:

Three LiveData corresponding to filters

Event to notify the user that no entries are found

Event to notify the user that no entries are present

Status of SwipeRefreshLayout

List of connections to show based on filter input

When user types text in filter ViewModel's LiveData gets notified about the changes and updates the data. I 've read that it's a bad practice to expose MutableLiveData to Activities/Fragments but they have to notify ViewModel about the changes somehow. When no entries are found based on the user's input Toast is shown.

The problem

When the user enters filter values that have no matches, Toast is shown. If the user then rotates the device Toast is shown again and again.

I've read these articles:

https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-case-ac2622673150

https://proandroiddev.com/livedata-with-single-events-2395dea972a8

But I don't understand how I can apply these to my use case. I think the problem in how I perform the updates

private val connections = connectionRepository.allConnections()
private val mediatorConnection = MediatorLiveData<List<Connection>>().also {
    it.value = connections.value
}

private val refreshLiveData = MutableLiveData(RefreshStatus.READY)
private val noMatchesEvent = SingleLiveEvent<Void>()
private val emptyTableEvent = SingleLiveEvent<Void>()

val nameLiveData = MutableLiveData<String>()
val urlLiveData = MutableLiveData<String>()
val actualStatusLiveData = MutableLiveData<String>()

init {
    with(mediatorConnection) {
        addSource(connections) { update() }
        addSource(nameLiveData) { update() }
        addSource(urlLiveData) { update() }
        addSource(actualStatusLiveData) { update() }
    }
}

fun getRefreshLiveData(): LiveData<RefreshStatus> = refreshLiveData
fun getNoMatchesEvent(): LiveData<Void> = noMatchesEvent
fun getEmptyTableEvent(): LiveData<Void> = emptyTableEvent
fun getConnections(): LiveData<List<Connection>> = mediatorConnection

private fun update() {
    if (connections.value.isNullOrEmpty()) {
        emptyTableEvent.call()
    } else {
        mediatorConnection.value = connections.value?.filter { connection -> getPredicate().test(connection) }

        if (mediatorConnection.value.isNullOrEmpty()) {
            noMatchesEvent.call()
        }
    }
}

update() gets triggered on screen rotation because of new subscription to mediatorConnection and MediatorLiveData.onActive() is called. And it's intented behavior

Android live data - observe always fires after config change

Code for showing toast

package com.github.aoreshin.shtatus.fragments

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.Toast
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.github.aoreshin.shtatus.R
import com.github.aoreshin.shtatus.ShatusApplication
import com.github.aoreshin.shtatus.viewmodels.ConnectionListViewModel
import javax.inject.Inject

class ConnectionListFragment : Fragment() {
    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory

    private lateinit var refreshLayout: SwipeRefreshLayout

    private lateinit var nameEt: EditText
    private lateinit var urlEt: EditText
    private lateinit var statusCodeEt: EditText

    private lateinit var viewModel: ConnectionListViewModel

    private lateinit var recyclerView: RecyclerView
    private lateinit var viewAdapter: ConnectionListAdapter

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_connection_list, container, false)

        val application = (requireActivity().application as ShatusApplication)
        application.appComponent.inject(this)

        val viewModelProvider = ViewModelProvider(this, viewModelFactory)
        viewModel = viewModelProvider.get(ConnectionListViewModel::class.java)

        bindViews(view)
        setupObservers()
        setupListeners()
        addFilterValues()
        setupRecyclerView()
        return view
    }

    private fun setupObservers() {
        with(viewModel) {
            getConnections().observe(viewLifecycleOwner, Observer { viewAdapter.submitList(it) })

            getRefreshLiveData().observe(viewLifecycleOwner, Observer { status ->
                when (status) {
                    ConnectionListViewModel.RefreshStatus.LOADING -> refreshLayout.isRefreshing = true
                    ConnectionListViewModel.RefreshStatus.READY -> refreshLayout.isRefreshing = false
                    else -> throwException(status.toString())
                }
            })

            getNoMatchesEvent().observe(viewLifecycleOwner, Observer { showToast(R.string.status_no_matches) })
            getEmptyTableEvent().observe(viewLifecycleOwner, Observer { showToast(R.string.status_no_connections) })
        }
    }

    private fun setupRecyclerView() {
        viewAdapter = ConnectionListAdapter(parentFragmentManager, ConnectionItemCallback())
        recyclerView.apply {
            layoutManager = LinearLayoutManager(context)
            adapter = viewAdapter
        }
    }

    private fun addFilterValues() {
        with(viewModel) {
            nameEt.setText(nameLiveData.value)
            urlEt.setText(urlLiveData.value)
            statusCodeEt.setText(actualStatusLiveData.value)
        }
    }

    private fun bindViews(view: View) {
        with(view) {
            recyclerView = findViewById(R.id.recycler_view)
            refreshLayout = findViewById(R.id.refresher)
            nameEt = findViewById(R.id.nameEt)
            urlEt = findViewById(R.id.urlEt)
            statusCodeEt = findViewById(R.id.statusCodeEt)
        }
    }

    private fun setupListeners() {
        with(viewModel) {
            refreshLayout.setOnRefreshListener { send() }
            nameEt.addTextChangedListener { nameLiveData.value = it.toString() }
            urlEt.addTextChangedListener { urlLiveData.value = it.toString() }
            statusCodeEt.addTextChangedListener { actualStatusLiveData.value = it.toString() }
        }
    }

    private fun throwException(status: String) {
        throw IllegalStateException(getString(R.string.error_no_such_status) + status)
    }

    private fun showToast(resourceId: Int) {
        Toast.makeText(context, getString(resourceId), Toast.LENGTH_SHORT).show()
    }

    override fun onDestroyView() {
        super.onDestroyView()
        with(viewModel) {
            getNoMatchesEvent().removeObservers(viewLifecycleOwner)
            getRefreshLiveData().removeObservers(viewLifecycleOwner)
            getEmptyTableEvent().removeObservers(viewLifecycleOwner)
            getConnections().removeObservers(viewLifecycleOwner)
        }
    }
}

How I should address this issue?

Body:
After some head scratching I've decided to go with internal ViewModel statuses, this way logic in Activity/Fragment is kept to a minimum.

So now my ViewModel looks like this:

package com.github.aoreshin.shtatus.viewmodels

import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.github.aoreshin.shtatus.events.SingleLiveEvent
import com.github.aoreshin.shtatus.room.Connection
import io.reactivex.FlowableSubscriber
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import io.reactivex.subscribers.DisposableSubscriber
import okhttp3.ResponseBody
import retrofit2.Response
import java.util.function.Predicate
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class ConnectionListViewModel @Inject constructor(
    private val connectionRepository: ConnectionRepository
) : ViewModel() {
    private var tableStatus = TableStatus.OK

    private val connections = connectionRepository.allConnections()
    private val mediatorConnection = MediatorLiveData<List<Connection>>()

    private val stopRefreshingEvent = SingleLiveEvent<Void>()
    private val noMatchesEvent = SingleLiveEvent<Void>()
    private val emptyTableEvent = SingleLiveEvent<Void>()

    private val nameLiveData = MutableLiveData<String>()
    private val urlLiveData = MutableLiveData<String>()
    private val statusLiveData = MutableLiveData<String>()

    init {
        with(mediatorConnection) {
            addSource(connections) { update() }
            addSource(nameLiveData) { update() }
            addSource(urlLiveData) { update() }
            addSource(statusLiveData) { update() }
        }
    }

    fun getStopRefreshingEvent(): LiveData<Void> = stopRefreshingEvent
    fun getNoMatchesEvent(): LiveData<Void> = noMatchesEvent
    fun getEmptyTableEvent(): LiveData<Void> = emptyTableEvent
    fun getConnections(): LiveData<List<Connection>> = mediatorConnection
    fun getName(): String? = nameLiveData.value
    fun getUrl(): String? = urlLiveData.value
    fun getStatus(): String? = statusLiveData.value
    fun setName(name: String) { nameLiveData.value = name }
    fun setUrl(url: String) { urlLiveData.value = url }
    fun setStatus(status: String) { statusLiveData.value = status }

    private fun update() {
        if (connections.value != null) {
            if (connections.value.isNullOrEmpty()) {
                if (tableStatus != TableStatus.EMPTY) {
                    emptyTableEvent.call()
                    tableStatus = TableStatus.EMPTY
                }
            } else {
                mediatorConnection.value = connections.value?.filter { connection -> getPredicate().test(connection) }

                if (mediatorConnection.value.isNullOrEmpty()) {
                    if (tableStatus != TableStatus.NO_MATCHES) {
                        noMatchesEvent.call()
                        tableStatus = TableStatus.NO_MATCHES
                    }
                } else {
                    tableStatus = TableStatus.OK
                }
            }
        }
    }

    fun send() {
        if (!connections.value.isNullOrEmpty()) {
            val singles = connections.value?.map { connection ->
                val id = connection.id
                val description = connection.description
                val url = connection.url
                var message = ""

                connectionRepository.sendRequest(url)
                    .doOnSuccess { message = it.code().toString() }
                    .doOnError { message = it.message!! }
                    .doFinally {
                        val result = Connection(id, description, url, message)
                        connectionRepository.insert(result)
                    }
            }

            Single.mergeDelayError(singles)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .doFinally { stopRefreshingEvent.call() }
                .subscribe(getSubscriber())
        } else {
            stopRefreshingEvent.call()
        }
    }

    private fun getSubscriber() : FlowableSubscriber<Response<ResponseBody>> {
        return object: DisposableSubscriber<Response<ResponseBody>>() {
            override fun onComplete() { Log.d(TAG, "All requests sent") }
            override fun onNext(t: Response<ResponseBody>?) { Log.d(TAG, "Request is done") }
            override fun onError(t: Throwable?) { Log.d(TAG, t!!.message!!) }
        }
    }

    private fun getPredicate(): Predicate<Connection> {
        return Predicate { connection ->
            connection.description.contains(nameLiveData.value.toString(), ignoreCase = true)
                    && connection.url.contains(urlLiveData.value.toString(), ignoreCase = true)
                    && connection.actualStatusCode.contains(
                statusLiveData.value.toString(),
                ignoreCase = true
            )
        }
    }

    private enum class TableStatus {
        NO_MATCHES,
        EMPTY,
        OK
    }

    companion object {
        private const val TAG = "ConnectionListViewModel"
    }
}

And corresponding Fragment looks like this:

package com.github.aoreshin.shtatus.fragments

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.Toast
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.github.aoreshin.shtatus.R
import com.github.aoreshin.shtatus.ShatusApplication
import com.github.aoreshin.shtatus.viewmodels.ConnectionListViewModel
import javax.inject.Inject

class ConnectionListFragment : Fragment() {
    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory

    private lateinit var refreshLayout: SwipeRefreshLayout

    private lateinit var nameEt: EditText
    private lateinit var urlEt: EditText
    private lateinit var statusCodeEt: EditText

    private lateinit var viewModel: ConnectionListViewModel

    private lateinit var recyclerView: RecyclerView
    private lateinit var viewAdapter: ConnectionListAdapter

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_connection_list, container, false)

        val application = (requireActivity().application as ShatusApplication)
        application.appComponent.inject(this)

        val viewModelProvider = ViewModelProvider(this, viewModelFactory)
        viewModel = viewModelProvider.get(ConnectionListViewModel::class.java)

        bindViews(view)
        setupObservers()
        setupListeners()
        addFilterValues()
        setupRecyclerView()
        return view
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        if (savedInstanceState != null) {
            refreshLayout.isRefreshing = savedInstanceState.getBoolean(REFRESHING, false)
        }
    }

    private fun setupObservers() {
        with(viewModel) {
            getConnections().observe(viewLifecycleOwner, Observer { viewAdapter.submitList(it) })
            getStopRefreshingEvent().observe(viewLifecycleOwner, Observer { refreshLayout.isRefreshing = false })
            getNoMatchesEvent().observe(viewLifecycleOwner, Observer { showToast(R.string.status_no_matches) })
            getEmptyTableEvent().observe(viewLifecycleOwner, Observer { showToast(R.string.status_no_connections) })
        }
    }

    private fun setupRecyclerView() {
        viewAdapter = ConnectionListAdapter(parentFragmentManager, ConnectionItemCallback())
        recyclerView.apply {
            layoutManager = LinearLayoutManager(context)
            adapter = viewAdapter
        }
    }

    private fun addFilterValues() {
        with(viewModel) {
            nameEt.setText(getName())
            urlEt.setText(getUrl())
            statusCodeEt.setText(getStatus())
        }
    }

    private fun bindViews(view: View) {
        with(view) {
            recyclerView = findViewById(R.id.recycler_view)
            refreshLayout = findViewById(R.id.refresher)
            nameEt = findViewById(R.id.nameEt)
            urlEt = findViewById(R.id.urlEt)
            statusCodeEt = findViewById(R.id.statusCodeEt)
        }
    }

    private fun setupListeners() {
        with(viewModel) {
            refreshLayout.setOnRefreshListener { send() }
            nameEt.addTextChangedListener { setName(it.toString()) }
            urlEt.addTextChangedListener { setUrl(it.toString()) }
            statusCodeEt.addTextChangedListener { setStatus(it.toString()) }
        }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putBoolean(REFRESHING, refreshLayout.isRefreshing)
    }

    private fun showToast(resourceId: Int) {
        Toast.makeText(context, getString(resourceId), Toast.LENGTH_SHORT).show()
    }

    override fun onDestroyView() {
        super.onDestroyView()
        with(viewModel) {
            getNoMatchesEvent().removeObservers(viewLifecycleOwner)
            getEmptyTableEvent().removeObservers(viewLifecycleOwner)
            getStopRefreshingEvent().removeObservers(viewLifecycleOwner)
            getConnections().removeObservers(viewLifecycleOwner)
        }
    }

    companion object {
        private const val REFRESHING = "isRefreshing"
    }
}

Pros

No additional dependencies

Usage of widespread SingleLiveEvent

Pretty straightforward to implement

Cons

Conditional logic is quickly getting out of hand even in this simple case, surely needs refactoring. Not sure if this approach will work in real-life complex scenarios.

If there are cleaner and more concise approaches to solve this problem I will be happy to hear about them!


来源: stackexchange

问:
Can Delphi created Soap server app run on linux?
I have been toying around with creating soap servers in Delphi then running and interacting with them being hosted on my localmachine via IIS.

I have a godaddy account that is linux hosting and I am curious if I'll be able to use the Delphi created Soap Servers on this hosting?

答:
Wine isn't going to be viable IMO.  Typically your SOAP service is going to be a command-line executable in the cgi-bin directory.  They typically have very short lifetimes (less than a second) so running natively is a big help here.<br>
Unless the system is smart enough to know that your Win-32 app needs to run under Wine, and not have to incurr the overhead of spinning up a Wine session, it won't work.  </p>

ContentLicense: CC BY-SA 3.0

Body:
Wine isn't going to be viable IMO.  Typically your SOAP service is going to be a command-line executable in the cgi-bin directory.  They typically have very short lifetimes (less than a second) so running natively is a big help here.

Unless the system is smart enough to know that your Win-32 app needs to run under Wine, and not have to incurr the overhead of spinning up a Wine session, it won't work.


问:
Using Tensorflow, How can I load weights generated from LSTM into CudnnLSTM Model?
I trained on LSTM model with tensorflow, can i load weights generated from LSTM into CudnnLSTM Model? My LSTM code is

```<code>lstm_cell = tf.contrib.rnn.LSTMCell(hidden_size)
outputs, (c, h) = tf.nn.dynamic_rnn(lstm_cell,
                                    input_seq,
                                    dtype = tf.float32)
</code>```

CudnnLSTM code is

```<code>cudnn_cell_fw = cudnn_rnn.CudnnLSTM(num_layers = 1,
                                    num_units = hidden_size,
                                    direction = cudnn_rnn.CUDNN_RNN_UNIDIRECTION,
                                    input_mode = cudnn_rnn.CUDNN_INPUT_LINEAR_MODE,
                                    dtype = tf.float32)
outputs, (h, c) = cudnn_cell_fw(inputs = input_seq)
</code>```

答:
I tried collecting weight and bias of LSTM as:

```<code>frozen_graph_path = './train_one_lstm_model.pb'
frozen_graphdef = get_graphdef(frozen_graph_path)
for node in frozen_graphdef.node:
    if (node.name == 'rnn/lstm_cell/kernel'):
        lstm_weight = tensor_util.MakeNdarray(node.attr['value'].tensor)
    if (node.name == 'rnn/lstm_cell/bias'):
        lstm_bias = tensor_util.MakeNdarray(node.attr['value'].tensor)
</code>```

Then i send them to CudnnLSTM node

```<code>weight_shape_0 = lstm_weight.shape[0]
weight_shape_1 = lstm_weight.shape[1]
new_cudnn_weight = np.zeros(((weight_shape_0 + 2) * weight_shape_1), dtype = np.float32)
index = 0
for i in range(weight_shape_0):
    for j in range(weight_shape_1):
        new_cudnn_weight[index] = lstm_weight[i][j]
        index += 1

for j in range(weight_shape_1):
    new_cudnn_weight[index] = lstm_bias[j]
    index += 1

frozen_graph_path = './train_one_culstm_model.pb'
frozen_graphdef = get_graphdef(frozen_graph_path)

for node in frozen_graphdef.node:
    if (node.name == 'cudnn_lstm/opaque_kernel'):
        ori_cudnn_weight = tensor_util.MakeNdarray(node.attr['value'].tensor)
        node.attr['value'].tensor.CopyFrom(tensor_util.make_tensor_proto(new_cudnn_weight))
        new_cudnn_weight = tensor_util.MakeNdarray(node.attr['value'].tensor)
</code>```

So the CudnnLSTM node get same weights and bias as LSTM. However, when i send same input, the output is different.

ContentLicense: CC BY-SA 4.0

Body:
I trained on LSTM model with tensorflow, can i load weights generated from LSTM into CudnnLSTM Model? My LSTM code is

lstm_cell = tf.contrib.rnn.LSTMCell(hidden_size)
outputs, (c, h) = tf.nn.dynamic_rnn(lstm_cell,
                                    input_seq,
                                    dtype = tf.float32)

CudnnLSTM code is

cudnn_cell_fw = cudnn_rnn.CudnnLSTM(num_layers = 1,
                                    num_units = hidden_size,
                                    direction = cudnn_rnn.CUDNN_RNN_UNIDIRECTION,
                                    input_mode = cudnn_rnn.CUDNN_INPUT_LINEAR_MODE,
                                    dtype = tf.float32)
outputs, (h, c) = cudnn_cell_fw(inputs = input_seq)

Body:
I tried collecting weight and bias of LSTM as:

frozen_graph_path = './train_one_lstm_model.pb'
frozen_graphdef = get_graphdef(frozen_graph_path)
for node in frozen_graphdef.node:
    if (node.name == 'rnn/lstm_cell/kernel'):
        lstm_weight = tensor_util.MakeNdarray(node.attr['value'].tensor)
    if (node.name == 'rnn/lstm_cell/bias'):
        lstm_bias = tensor_util.MakeNdarray(node.attr['value'].tensor)

Then i send them to CudnnLSTM node

weight_shape_0 = lstm_weight.shape[0]
weight_shape_1 = lstm_weight.shape[1]
new_cudnn_weight = np.zeros(((weight_shape_0 + 2) * weight_shape_1), dtype = np.float32)
index = 0
for i in range(weight_shape_0):
    for j in range(weight_shape_1):
        new_cudnn_weight[index] = lstm_weight[i][j]
        index += 1

for j in range(weight_shape_1):
    new_cudnn_weight[index] = lstm_bias[j]
    index += 1

frozen_graph_path = './train_one_culstm_model.pb'
frozen_graphdef = get_graphdef(frozen_graph_path)

for node in frozen_graphdef.node:
    if (node.name == 'cudnn_lstm/opaque_kernel'):
        ori_cudnn_weight = tensor_util.MakeNdarray(node.attr['value'].tensor)
        node.attr['value'].tensor.CopyFrom(tensor_util.make_tensor_proto(new_cudnn_weight))
        new_cudnn_weight = tensor_util.MakeNdarray(node.attr['value'].tensor)

So the CudnnLSTM node get same weights and bias as LSTM. However, when i send same input, the output is different.


问:
Deserializing An Unknown JSON Format
EDIT: My bad bros, I meant deserializing not serializing...

I am trying to interpret the JSON response I get from a website, but there are a two problems:

1) First version of the JSON has a list of an unknown number of strings, and I don't think I can create a class with a List<string> {get; set;} member.

2) I cannot know which version I will receive and in order to tell I will have to check for the value of "Second". I am wondering if there may be an easier way to doing this?

These are the two possible formats:

```<code>{ "1":"1", "2":"2", "Second":{ "3":"3", "4":"4", "Third":["5"]}} <-- More things could be in 'Third'
{ "1":"1", "2":"2", "Second":{ "3":"3", "4":"4", "Third":{"5":"5", "6":"6"}}
</code>```

I deserialize them using Newtonsoft's Json C# package and I receive the response from a HttpClient.PostAsync() call.

答:
Deserialising to a list should work ok. As @maytham says, a custom converter is the way to go.

The SpecialArrayConverter below should do the trick if you apply it to your model via the [JsonConverter(typeof(SpecialArrayConverter))] attribute.

As implemented below it'll read a dictionary or array in the json and populate the corresponding collection in the deserialised class.

I've typed the property as IEnumable<string> out of habit, but List<string> work just as well.  string[] can work with a bit of tweaking too.

using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Json
{
    class JsonResponseObject
    {
        public string Id { get; set; }

        // Apply our special converter to this property
        [JsonConverter(typeof(SpecialArrayConverter))]
        public IEnumerable<string> ArrayData { get; set; }
    }

    // Our Special Converter
    class SpecialArrayConverter : JsonConverter
    {
        // Read Only
        public override bool CanWrite => false;

        // We only work with IEnumerable<string> && IDictionary<string, string>
        public override bool CanConvert(Type objectType)
        {
            return objectType.IsAssignableFrom(typeof(IEnumerable<string>)) || objectType.IsAssignableFrom(typeof(IDictionary<string, string>));
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            JToken token;

            switch (reader.TokenType)
            {
                // If it's an array, read the token parse it
                // into an array and proceeed
                case JsonToken.StartArray:
                    token = JToken.Load(reader);
                    return token.ToObject<List<string>>();

                // If it's an object, it must be a dictionary
                // parse it into a dictionary and then
                // output the keys into an array and
                // return that
                case JsonToken.StartObject:
                    token = JToken.Load(reader);
                    var data = token.ToObject<Dictionary<string, string>>();
                    return data
                        .Select(data => data.Key)
                        .ToList();
                default:
                    throw new JsonSerializationException();
            }
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var a = JsonConvert.SerializeObject(new
            {
                ArrayData = new Dictionary<string, string>
                {
                    { "5", "5" },
                    { "6", "6" }
                }
            });
            var b = JsonConvert.SerializeObject(new
            {
                ArrayData = new List<string>
                {
                    "5",
                    "6"
                }
            });

            var resulta = JsonConvert.DeserializeObject<JsonResponseObject>(a);
            var resultb = JsonConvert.DeserializeObject<JsonResponseObject>(b);

            Console.WriteLine("Hello World!");
        }
    }
}

```

Title: Deserializing An Unknown JSON Format

Body:
EDIT: My bad bros, I meant deserializing not serializing...

I am trying to interpret the JSON response I get from a website, but there are a two problems:

1) First version of the JSON has a list of an unknown number of strings, and I don't think I can create a class with a List<string> {get; set;} member.

2) I cannot know which version I will receive and in order to tell I will have to check for the value of "Second". I am wondering if there may be an easier way to doing this?

These are the two possible formats:

{ "1":"1", "2":"2", "Second":{ "3":"3", "4":"4", "Third":["5"]}} <-- More things could be in 'Third'
{ "1":"1", "2":"2", "Second":{ "3":"3", "4":"4", "Third":{"5":"5", "6":"6"}}

I deserialize them using Newtonsoft's Json C# package and I receive the response from a HttpClient.PostAsync() call.


问:
When posting a page from my website on LinkedIn, - Building HTML pages with 'og' meta tags to be LinkedIn friendly
How does one control what image and title appear when posting a URL to LinkedIn?
I manage my company's website efficienty and write all the HTML, CSS, jS and PHP when necessary.  One nut I cannot crack however is getting LinkedIn to pull the correct image and title when I make a post.

LinkedIn published the following to allow og tags to solve this problem:

I am having difficulty making this work

This is key for me and my team for we frequently post stories, case studies and advertisements which we can't share on LinkdIn because LinkedIn logic usually (not always) doesn't pick up any of the images in our HTML.
Is there a meta tag or something I need to embed in the HTML line?

答:
You can use this tool to test https://www.linkedin.com/post-inspector/inspect/ your page.
Linkedin seems to be using the open graph protocol from Facebook https://ogp.me/</p>

```<code><meta name="image" property="og:image" content="[Image URL here]">
</code>```

Body:
You can use this tool to test https://www.linkedin.com/post-inspector/inspect/ your page.
Linkedin seems to be using the open graph protocol from Facebook https://ogp.me/

<meta name="image" property="og:image" content="[Image URL here]">


问:
Can an agora project handle multiple live broadcast rooms?
The context is to create a live streaming android app using Agora services. I am unsure if a single agora project can handle all the live stream rooms. According to the agora tutorial, I used the temporary token which required the channel name and app id.

I am wondering if multiple broadcasters want to create channels with different names, is agora able to run multiple rooms simultaneously? How do I do so using the temporary token?

If yes, will the rooms have different tokens?

Will there be any drastic changes if I proceed to use the app certificate to produce the token?

答: you can definitely join multiple channels using Agora. Here is the documents for joining multiple channels: https://docs.agora.io/en/Video/multiple_channel_android?platform=Android. You can use the token generator to generate the token from the server and it is not hard to do. You can see the instruction here: https://docs.agora.io/en/Video/token_server_cpp?platform=CPP.</p>


问:
How can I create an airplane radar using python graphics? (draw)
```<code>from graphics import*

def main():

    win = GraphWin("My Window",500,500)
    win.setBackground(color_rgb(0,0,0))

#Point
    pt = Point(250,250)
    pt.setOutline(color_rgb(255,255,0))
    pt.draw(win)

    win.getMouse()
    win.close()

main()
</code>```

How can I make the lines, the circle, and erase the background color so the result looks like the illustration?

答:
This module is known also as graphics.py or Zelle's graphics and you should use these names to search more information.

Home Page for graphics.py with documentation and examples

Hands-on Python Tutorial > Graphics

You could also check source code to see all classes and functions.

This module is built on top of tkinter so you may have to learn also tkinter for some functions which are not available directly in graphics.py but you still can use it.

For example tkinter has after(milliseconds, function_name) to execute function with delay or repeate the same function after delay - so you can use it to create animation.

BTW: in after() has to be function's name without () and without arguments - and later tkinter will use () to execute it. It is called callback.

Example which uses after() to move point. This way it animates element and you still can click window to close program.

```<code>from graphics import * # PEP8: `import *` is not preferred
import random

# --- functions ---

def move():
    dx = random.randint(-10, 10)
    dy = random.randint(-10, 10)
    pt.move(dx, dy)
    # move again after 100ms (0.1s)
    win.after(100, move)

# --- main ---

win = GraphWin("My Window",500,500)
win.setBackground(color_rgb(0,0,0))

pt = Point(250, 250)
pt.setOutline(color_rgb(255,255,0))
pt.draw(win)

# move first time after 100ms (0.1s)
win.after(100, move)

#win.mainloop()
win.getMouse()

win.close()
</code>```

EDIT: after digging in information I created version without after()

```<code>from graphics import * # PEP8: `import *` is not preferred
import random
import time

# --- main ---

win = GraphWin("My Window",500,500)
win.setBackground(color_rgb(0,0,0))

pt = Point(250, 250)
pt.setOutline(color_rgb(255,255,0))
pt.draw(win)

while True:
    if win.checkMouse():
        break
    dx = random.randint(-10, 10)
    dy = random.randint(-10, 10)
    pt.move(dx, dy)
    time.sleep(0.1)

win.close()
</code>```

Body:
from graphics import*

def main():

    win = GraphWin("My Window",500,500)
    win.setBackground(color_rgb(0,0,0))

#Point
    pt = Point(250,250)
    pt.setOutline(color_rgb(255,255,0))
    pt.draw(win)

    win.getMouse()
    win.close()

main()

How can I make the lines, the circle, and erase the background color so the result looks like the illustration?

Body:
This module is known also as graphics.py or Zelle's graphics and you should use these names to search more information.

Home Page for graphics.py with documentation and examples

Hands-on Python Tutorial > Graphics

You could also check source code to see all classes and functions.

This module is built on top of tkinter so you may have to learn also tkinter for some functions which are not available directly in graphics.py but you still can use it.

For example tkinter has after(milliseconds, function_name) to execute function with delay or repeate the same function after delay - so you can use it to create animation.

BTW: in after() has to be function's name without () and without arguments - and later tkinter will use () to execute it. It is called callback.

Example which uses after() to move point. This way it animates element and you still can click window to close program.

from graphics import * # PEP8: `import *` is not preferred
import random

# --- functions ---

def move():
    dx = random.randint(-10, 10)
    dy = random.randint(-10, 10)
    pt.move(dx, dy)
    # move again after 100ms (0.1s)
    win.after(100, move)

# --- main ---

win = GraphWin("My Window",500,500)
win.setBackground(color_rgb(0,0,0))

pt = Point(250, 250)
pt.setOutline(color_rgb(255,255,0))
pt.draw(win)

# move first time after 100ms (0.1s)
win.after(100, move)

#win.mainloop()
win.getMouse()

win.close()

EDIT: after digging in information I created version without after()

from graphics import * # PEP8: `import *` is not preferred
import random
import time

# --- main ---

win = GraphWin("My Window",500,500)
win.setBackground(color_rgb(0,0,0))

pt = Point(250, 250)
pt.setOutline(color_rgb(255,255,0))
pt.draw(win)

while True:
    if win.checkMouse():
        break
    dx = random.randint(-10, 10)
    dy = random.randint(-10, 10)
    pt.move(dx, dy)
    time.sleep(0.1)

win.close()


问:
How to append output to the end of a text file
How do I append the output of a command to the end of a text file?</p>

答:
You can use the >> operator. This will append data from a command to the end of a text file.

To test this try running:

```<code>echo "Hi this is a test" >> textfile.txt
</code>```

Do this a couple of times and then run:

```<code>cat textfile.txt
</code>```

You'll see your text has been appended several times to the textfile.txt file.

Body:
You can use the >> operator. This will append data from a command to the end of a text file.

To test this try running:

echo "Hi this is a test" >> textfile.txt

Do this a couple of times and then run:

cat textfile.txt

You'll see your text has been appended several times to the textfile.txt file.


问:
jstree: Select a node in jstree by name attribute rather than id
I need to select a node in the jstree by it's attribute name. Then after selecting that node I need to get it's id.
Basically I want to get the id of a node given its name.

I tried the following code:

```<code>$("#tree1").bind("loaded.jstree", function (e, data) {

var isa_name = "ISA16469";

//$("#tree1").jstree("select_node", "#01"); //this code works but it selects by id, I want to select by attribute name

$("#tree1").jstree("select_node", $("li[name='" + isa_name + "']")); //doesn't work

var test = $('#tree1').jstree('get_selected').attr('id'); //get id of selected node
alert(test)

})
</code>```

Your help is most welcome. Thanks a lot

答:
Here is a function that I have written from vakata's fiddle. Set 'MyAttribute' to your attribute. Pass 'MyAttribute's Value to the function and it will select your JSTree node by attribute.</p>

```<code>//SELECT JSTREE by Attribute
function selectNodeByMyAttribute (AttrValue) {
    $("#jstree").jstree().deselect_all(true);

    var instance = $("#jstree").jstree(true);
    var m = instance._model.data;
    for (var i in m) {
        if (m.hasOwnProperty(i) && i !== '#' && m[i].li_attr.MyAttribute && m[i].li_attr.MyAttribute === AttrValue) {
            instance.select_node(i);
            break;
        }
    }
}
</code>```

Body:
I need to select a node in the jstree by it's attribute name. Then after selecting that node I need to get it's id.
Basically I want to get the id of a node given its name.

I tried the following code:

$("#tree1").bind("loaded.jstree", function (e, data) {

var isa_name = "ISA16469";

//$("#tree1").jstree("select_node", "#01"); //this code works but it selects by id, I want to select by attribute name

$("#tree1").jstree("select_node", $("li[name='" + isa_name + "']")); //doesn't work

var test = $('#tree1').jstree('get_selected').attr('id'); //get id of selected node
alert(test)

})

Your help is most welcome. Thanks a lot

Body:
Here is a function that I have written from vakata's fiddle. Set 'MyAttribute' to your attribute. Pass 'MyAttribute's Value to the function and it will select your JSTree node by attribute.

//SELECT JSTREE by Attribute
function selectNodeByMyAttribute (AttrValue) {
    $("#jstree").jstree().deselect_all(true);

    var instance = $("#jstree").jstree(true);
    var m = instance._model.data;
    for (var i in m) {
        if (m.hasOwnProperty(i) && i !== '#' && m[i].li_attr.MyAttribute && m[i].li_attr.MyAttribute === AttrValue) {
            instance.select_node(i);
            break;
        }
    }
}


答:
We can use tidyverse to create an 'index_event' after grouping by 'id_question'.  If the number of rows are greater than 1 (n() >1), then get the sequence of rows (row_number()) and the default option in case_when is NA

```<code>library(dplyr)
df1 %>%
   group_by(id_question) %>%
   mutate(index_event = case_when(n() >1 ~ row_number()))
# A tibble: 10 x 4
# Groups:   id_question [6]
#   id_question id_event num_events index_event
#         <int>    <int>      <int>       <int>
# 1  2015012713    49508          1          NA
# 2  2015012711    49708          1          NA
# 3  2015011523    41808          3           1
# 4  2015011523    44008          3           2
# 5  2015011523    44108          3           3
# 6  2015011522    41508          3           1
# 7  2015011522    43608          3           2
# 8  2015011522    43708          3           3
# 9  2015011521    39708          1          NA
#10  2015011519    44208          1          NA
</code>```

Or with data.table, we use rowid on 'id_question' and change the elements that are 1 in 'num_events' to NA with NA^ (making use of NA^0, NA^1)

```<code>library(data.table)
setDT(df1)[, index_event := rowid(id_question) * NA^(num_events == 1)]
</code>```

Or using base R, another option with the sequence of frequency from 'id_question' and change elements to NA as in the previous case

```<code>df1$index_event <-  with(df1, sequence(table(id_question)) * NA^(num_events == 1))
df1$index_event
#[1] NA NA  1  2  3  1  2  3 NA NA
</code>```

data

```<code>df1 <- structure(list(id_question = c(2015012713L, 2015012711L, 2015011523L,
2015011523L, 2015011523L, 2015011522L, 2015011522L, 2015011522L,
2015011521L, 2015011519L), id_event = c(49508L, 49708L, 41808L,
44008L, 44108L, 41508L, 43608L, 43708L, 39708L, 44208L), num_events = c(1L,
1L, 3L, 3L, 3L, 3L, 3L, 3L, 1L, 1L)), class = "data.frame", row.names = c(NA,
-10L))
</code>```

Title: How do I create an index variable for unique values of X within a group Y?

Body:
I have the following table:

id_question  id_event   num_events
2015012713    49508          1
2015012711    49708          1
2015011523    41808          3
2015011523    44008          3
2015011523    44108          3
2015011522    41508          3
2015011522    43608          3
2015011522    43708          3
2015011521    39708          1
2015011519    44208          1

The third column gives the count of events by question. I want to create a variable that would index the events by question only where there are multiple events per question. It would look something like that:

id_question  id_event   num_events  index_event
2015012713    49508          1
2015012711    49708          1
2015011523    41808          3          1
2015011523    44008          3          2
2015011523    44108          3          3
2015011522    41508          3          1
2015011522    43608          3          2
2015011522    43708          3          3
2015011521    39708          1
2015011519    44208          1

How can I do that?

Body:
We can use tidyverse to create an 'index_event' after grouping by 'id_question'.  If the number of rows are greater than 1 (n() >1), then get the sequence of rows (row_number()) and the default option in case_when is NA

library(dplyr)
df1 %>%
   group_by(id_question) %>%
   mutate(index_event = case_when(n() >1 ~ row_number()))
# A tibble: 10 x 4
# Groups:   id_question [6]
#   id_question id_event num_events index_event
#         <int>    <int>      <int>       <int>
# 1  2015012713    49508          1          NA
# 2  2015012711    49708          1          NA
# 3  2015011523    41808          3           1
# 4  2015011523    44008          3           2
# 5  2015011523    44108          3           3
# 6  2015011522    41508          3           1
# 7  2015011522    43608          3           2
# 8  2015011522    43708          3           3
# 9  2015011521    39708          1          NA
#10  2015011519    44208          1          NA

Or with data.table, we use rowid on 'id_question' and change the elements that are 1 in 'num_events' to NA with NA^ (making use of NA^0, NA^1)

library(data.table)
setDT(df1)[, index_event := rowid(id_question) * NA^(num_events == 1)]

Or using base R, another option with the sequence of frequency from 'id_question' and change elements to NA as in the previous case

df1$index_event <-  with(df1, sequence(table(id_question)) * NA^(num_events == 1))
df1$index_event
#[1] NA NA  1  2  3  1  2  3 NA NA

data

df1 <- structure(list(id_question = c(2015012713L, 2015012711L, 2015011523L,
2015011523L, 2015011523L, 2015011522L, 2015011522L, 2015011522L,
2015011521L, 2015011519L), id_event = c(49508L, 49708L, 41808L,
44008L, 44108L, 41508L, 43608L, 43708L, 39708L, 44208L), num_events = c(1L,
1L, 3L, 3L, 3L, 3L, 3L, 3L, 1L, 1L)), class = "data.frame", row.names = c(NA,
-10L))


问:
PHP 7.2 with mcrypt in Windows
There are some huge legacy systems whose dependencies on PHPs' mcrypt are extremely important and vital (including the data storage in database). I need to find a way to maintain this library while upgrading to PHP 7.2 (which already worked fine).

My local test environment is Windows. The live environment is run on CentOS.

Some of the answers I have seen is decrypting and change mcrypt to openssl (I think that's not possible at the moment since there's a lot of data to decrypt).

Another way lights to download a lower PHP version with mcrypt-support, copy the extension and add it to php.ini (I do not even know the folder).

Downgrading PHP to 5.6 it's not suitable due to security issues.

Any light in what could be done in this scenario?

答:
Despite all the warnings and suggestions if you still need to make it work, try this:

Locate your PHP directory. Usually it is located on C:\Program
Files\PHP\v7.2

Then go to this url:
http://pecl.php.net/package/mcrypt/1.0.3/windows

Download the package that better meets your requirements. For example if
you are
    using FastCGI and have a 64 bit Windows installation pick
        7.2 Non Thread Safe (NTS) x64

Open the zip and copy php_mcrypt.dll file to the C:\Program
Files\PHP\v7.2\ext directory.

Edit the php.ini file and add this to the Dynamic extensions
    section:
    extension=php_mcrypt.dll

Save php.ini and restart your web server.

Hope it helps.

Title: PHP 7.2 with mcrypt in Windows


问:
Does AudioWorklet provide the lowest latency for playing sound Chrome on PC and Android?
I tried audio html tag, tonejs, audio web api and I still have about 110 ms with both android and PC's browser (chrome).

I saw there is this JavaScript audio worlet ( super() ) but I can't figure out if it's possible to play samples in this process

答:
Yes, the AudioWorklet's latency will be < 3ms (128 samples / 48,000hz = 2.67ms) and can function as a player by sending your decoded PCM samples to the output buffers in process().  AudioWorklets are not widely supported in browsers, so AudioBuffer is a good cross-browser alternative and works very well.

This example loads a bunch of audio files and instantly switches playback between them: https://opus-bitrates.anthum.com

audio-player-worklet.js

```<code>class AudioPlayer extends AudioWorkletProcessor {
  decodedAudio  // Float32Array data to play (interleaved or separate)

  constructor() {
    super()

    // set audio to play when received from main/worker thread
    this.port.onmessage = ({ data: { decodedAudio } }) => {
      this.decodedAudio = decodedAudio
    }
  }

  process(inputs, outputs) {
    if (this.decodedPCMAudio) {
      // copy 128 samples from decodedAudio to outputs channels
    }

    // run process() again for next 128 samples
    return true
  }
}

registerProcessor('audio-player', AudioPlayer)
</code>```

Body:
Yes, the AudioWorklet's latency will be < 3ms (128 samples / 48,000hz = 2.67ms) and can function as a player by sending your decoded PCM samples to the output buffers in process().  AudioWorklets are not widely supported in browsers, so AudioBuffer is a good cross-browser alternative and works very well.

This example loads a bunch of audio files and instantly switches playback between them: https://opus-bitrates.anthum.com

audio-player-worklet.js

class AudioPlayer extends AudioWorkletProcessor {
  decodedAudio  // Float32Array data to play (interleaved or separate)

  constructor() {
    super()

    // set audio to play when received from main/worker thread
    this.port.onmessage = ({ data: { decodedAudio } }) => {
      this.decodedAudio = decodedAudio
    }
  }

  process(inputs, outputs) {
    if (this.decodedPCMAudio) {
      // copy 128 samples from decodedAudio to outputs channels
    }

    // run process() again for next 128 samples
    return true
  }
}

registerProcessor('audio-player', AudioPlayer)


问:
preventing actioncable subscribers from submitting messages to chatrooms that they are not a part of in Rails
If my app uses AJAX to submit a user's message to a server side speak method that has no need for a controller (and therefore no space to write a before_filter), how would I implement behavior that restricts users who are not a part of that conversation from submitting messages through the browser console?

If I open my app and type App.messages.speak("hi", #guess random number#) that message is submitted to the conversation whether they are a part of it or not.

messages_channel.rb

```<code>class MessagesChannel < ApplicationCable::Channel
 def speak(data)
  Message.create! body: data['body'], conversation_id: data['conversation_id'], user_id: current_user.id
 end
end
</code>```

messages.coffee

```<code>App.messages = App.cable.subscriptions.create "MessagesChannel",
 speak: (body, conversation_id) ->
  @perform 'speak', body: body, conversation_id: conversation_id

 submit_message = () ->
 $('#response').on 'keydown', (event) ->
  if event.keyCode is 13
    conversation_id = $("[data-conversation-id]").data("conversation-id")
    App.messages.speak(event.target.value, conversation_id)
    event.target.value = ""
    event.preventDefault()
</code>```

I'm thinking I could remove the conversation_id param from the speak method and set it elsewhere that isn't dependent on user input? Currently I have it set as a data attribute, which means people could still mess with it.

答:
Solved it with a callback.

```<code>class Message < ApplicationRecord
 before_create :exclude_non_participants

 def exclude_non_participants
  if conversation.participates?(user)
   true
  else
   throw :abort
  end
 end
end
</code>```

participates?(user) in this case is:

```<code>class Conversation < ApplicationRecord
 def participates?(user)
 sender == user || receiver == user
 end
end
</code>```

If there's a better way to do this, please let me know. For now, I think this works?

Body:
If my app uses AJAX to submit a user's message to a server side speak method that has no need for a controller (and therefore no space to write a before_filter), how would I implement behavior that restricts users who are not a part of that conversation from submitting messages through the browser console?

If I open my app and type App.messages.speak("hi", #guess random number#) that message is submitted to the conversation whether they are a part of it or not.

messages_channel.rb

class MessagesChannel < ApplicationCable::Channel
 def speak(data)
  Message.create! body: data['body'], conversation_id: data['conversation_id'], user_id: current_user.id
 end
end

messages.coffee

App.messages = App.cable.subscriptions.create "MessagesChannel",
 speak: (body, conversation_id) ->
  @perform 'speak', body: body, conversation_id: conversation_id

 submit_message = () ->
 $('#response').on 'keydown', (event) ->
  if event.keyCode is 13
    conversation_id = $("[data-conversation-id]").data("conversation-id")
    App.messages.speak(event.target.value, conversation_id)
    event.target.value = ""
    event.preventDefault()

I'm thinking I could remove the conversation_id param from the speak method and set it elsewhere that isn't dependent on user input? Currently I have it set as a data attribute, which means people could still mess with it.

Body:
Solved it with a callback.

class Message < ApplicationRecord
 before_create :exclude_non_participants

 def exclude_non_participants
  if conversation.participates?(user)
   true
  else
   throw :abort
  end
 end
end

participates?(user) in this case is:

class Conversation < ApplicationRecord
 def participates?(user)
 sender == user || receiver == user
 end
end

If there's a better way to do this, please let me know. For now, I think this works?


问:
PHP/SQL problem: Data from sql wont display
I have this problem on my payroll processing system.
Below the area where the data are added is a table where the data added into the pay table are shown (except for the empID since the payroll details shown on the table must be from those that are associated to the employee that was selected.)

Payroll System image

Please refer to the link above to see image to further understand what's written below.

*[A]*is where the user selects which employee's payroll details be updated. After selecting the radio button and clicking the "Update Payroll Details" button, the user shall be brought to the Update Payroll Details page which is B.

*[B]*The upper part is where the user enters all the payroll details associated to the employee selected. Once the user clicks save the data shall be added to the database (pay table) and these details shall be shown on the table located below the input area.

However, although I have data in my pay table it wouldn't seem to display on the table below that's why the message shows "No Records Found". Here is my code for the entire Update Payroll Data page:

```<code>elseif ($_GET["k"]==41)
{
    session_start();
    if (isset($_SESSION["uname"]))
    {
            $empID=$_POST["empID"];
            $aBulan=0; $aPetsa=0; $aTuig=0; $tuigYana=date("Y"); $tuigHadto=$tuigYana-5;

            $anoBulan=array("January","February","March","April","May","June","July","August","September","October","November","December");

            if ($_POST["sButton"]=="Update Payroll Details")
            {
                    $anoSulod ="Payroll Details<hr>";
                    $anoSulod.="<form method='post' action='logged.php?k=42'>";
                    $anoSulod.="<input type='hidden' name='empID' value='".$empID."'>";
                    $anoSulod.="<table>";
                    $anoSulod.="    <tr><td>Name of employee : </td><td>".accessDatabase::hinoEmployee($empID)."</td></tr>";
                    $anoSulod.="    <tr>";
                    $anoSulod.="            <td>For the month ending : </td>";
                    $anoSulod.="            <td>";

// Kanan month ini dinhi
                    $anoSulod.="                    <select name='bBulan'>";
                    for ($bulan=1; $bulan<=12; $bulan++)
                    {
                        if ($bulan==$aBulan) $anoSulod.="       <option value='".$bulan."' selected>".$anoBulan[$bulan-1];
                        else $anoSulod.="       <option value='".$bulan."'>".$anoBulan[$bulan-1];
                    }
                    $anoSulod.="                    </select> ";

// Kanan year ini dinhi
                    $anoSulod.="                    <select name='bTuig'>";
                    for ($tuig=$tuigYana; $tuig>=$tuigHadto; $tuig--)
                    {
                        if ($tuig==$aTuig) $anoSulod.=" <option value='".$tuig."' selected>".$tuig;
                        else $anoSulod.="       <option value='".$tuig."'>".$tuig;
                    }
                    $anoSulod.="                    </select>";
                    $anoSulod.="            </td>";
                    $anoSulod.="    <tr>";
                    $anoSulod.="    <tr><td>Basic Salary : PhP</td><td><input type='text' name='bs'></td></tr>";
                    $anoSulod.="    <tr><td>PERA/ACA : PhP</td><td><input type='text' name='pa'></td></tr>";
                    $anoSulod.="    <tr><td>PAIC : PhP</td><td><input type='text' name='pc'></td></tr>";
                    $anoSulod.="    <tr><td>REL : PhP</td><td><input type='text' name='rl'></td></tr>";
                    $anoSulod.="    <tr><td>GSIS Premium/Loans : PhP</td><td><input type='text' name='gs'></td></tr>";
                    $anoSulod.="    <tr><td>HDMF Premium/Loans : PhP</td><td><input type='text' name='hd'></td></tr>";
                    $anoSulod.="    <tr><td>Coop Loans : PhP</td><td><input type='text' name='cl'></td></tr>";
                    $anoSulod.="    <tr><td>Union Dues : PhP</td><td><input type='text' name='ud'></td></tr>";
                    $anoSulod.="    <tr><td>Withholding Tax : PhP</td><td><input type='text' name='tx'></td></tr>";
                    $anoSulod.="    <tr><td colspan='2'><hr></tr>";
                    $anoSulod.="    <tr><td colspan='2'><input type='submit' value='Save'></tr>";
                    $anoSulod.="    <tr><td colspan='2'><hr></tr>";
                    $anoSulod.="    <tr>";
                    $anoSulod.="     <td colspan='2'>";
                    $anoSulod.="     <table border='1'>";

                    $anoSulod.="     <caption>Payroll Details</caption>";
                    $anoSulod.="     <tr>";
                    $anoSulod.="     <td> </td>";
                    $anoSulod.="     <td>Pay period</td>";
                    $anoSulod.="     <td>Basic Salary</td>";
                    $anoSulod.="     <td>PERA/ACA</td>";
                    $anoSulod.="     <td>PAIC</td>";
                    $anoSulod.="     <td>REL</td>";
                    $anoSulod.="     <td>GSIS Premium/Loans</td>";
                    $anoSulod.="     <td>HDMF Premium/Loans</td>";
                    $anoSulod.="     <td>Coop Loans</td>";
                    $anoSulod.="     <td>Union Dues</td>";
                    $anoSulod.="     <td>Withholding Tax</td>";
                    $anoSulod.="     </tr>";

                    $k="select b.payID, a.empname, date_format(b.date,'%M %Y') as date, b.bs, b.pa, b.pc, b.rl, b.gs, b.hd, b.cl, b.ud, b.tx, from emp a, pay b where a.empID=b.empID and a.empID=".$empID;
                    $q=mysql_query($k);
                    if (mysql_num_rows($q)>0)
                    {
                        while ($akonRows=mysql_fetch_array($q))
                        {
                            $anoSulod.="    <tr>";
                            $anoSulod.="     <td><input type='radio' name='payID' value='".$akonRows["payID"]."'></td>";
                            $anoSulod.="     <td>".$akonRows["date"]."</td>";
                            $anoSulod.="     <td>".$akonRows["bs"]."</td>";
                            $anoSulod.="     <td>".$akonRows["pa"]."</td>";
                            $anoSulod.="     <td>".$akonRows["pc"]."</td>";
                            $anoSulod.="     <td>".$akonRows["rl"]."</td>";
                            $anoSulod.="     <td>".$akonRows["gs"]."</td>";
                            $anoSulod.="     <td>".$akonRows["hd"]."</td>";
                            $anoSulod.="     <td>".$akonRows["cl"]."</td>";
                            $anoSulod.="     <td>".$akonRows["ud"]."</td>";
                            $anoSulod.="     <td>".$akonRows["tx"]."</td>";
                            $anoSulod.="    </tr>";
                        }
                    }
                    else
                    {
                        $anoSulod.="    <tr>";
                        $anoSulod.="     <td colspan='11' align='center'>No records found</td>";
                        $anoSulod.="    </tr>";
                    }

                    $anoSulod.="     </table>";

                    $anoSulod.="     </td>";
                    $anoSulod.="    </tr>";
                    $anoSulod.="</table>";
                    $anoSulod.="</form>";
                    $kadamoTD=2;
           }

      }
}
</code>```

Please help. I really can't figure out what went wrong. Please do... Thanks so much!

答:
The code is real mess. On top of my head and first mistake which I spot:

$empID=$_POST["empID"];

after that, several rows bellow you show the form and than submit that form. on that form you have

<input type='hidden' name='empID' value='".$empID."'>

I presume you end to that page via $_GET request, because otherwise you can pass one of your other checks elseif ($_GET["k"]==41). So, if I'm right with that, than you didn't have initial $_POST["empID"], so your $empID variable will be empty.

In general why did you mess so much $_GET and $_POST and not stick one one of them?

hint one of the easiest way to check above is to echo the $empID or the query

Body:
I have this problem on my payroll processing system.
Below the area where the data are added is a table where the data added into the pay table are shown (except for the empID since the payroll details shown on the table must be from those that are associated to the employee that was selected.)

Payroll System image

Please refer to the link above to see image to further understand what's written below.

*[A]*is where the user selects which employee's payroll details be updated. After selecting the radio button and clicking the "Update Payroll Details" button, the user shall be brought to the Update Payroll Details page which is B.

*[B]*The upper part is where the user enters all the payroll details associated to the employee selected. Once the user clicks save the data shall be added to the database (pay table) and these details shall be shown on the table located below the input area.

However, although I have data in my pay table it wouldn't seem to display on the table below that's why the message shows "No Records Found". Here is my code for the entire Update Payroll Data page:

elseif ($_GET["k"]==41)
{
    session_start();
    if (isset($_SESSION["uname"]))
    {
            $empID=$_POST["empID"];
            $aBulan=0; $aPetsa=0; $aTuig=0; $tuigYana=date("Y"); $tuigHadto=$tuigYana-5;

            $anoBulan=array("January","February","March","April","May","June","July","August","September","October","November","December");

            if ($_POST["sButton"]=="Update Payroll Details")
            {
                    $anoSulod ="Payroll Details
";
                    $anoSulod.="<form method='post' action='logged.php?k=42'>";
                    $anoSulod.="<input type='hidden' name='empID' value='".$empID."'>";
                    $anoSulod.="
";
                    $anoSulod.="
Name of employee : 	".accessDatabase::hinoEmployee($empID)."
";
                    $anoSulod.="
";
                    $anoSulod.="            For the month ending : 	";
                    $anoSulod.="            ";

// Kanan month ini dinhi
                    $anoSulod.="                    <select name='bBulan'>";
                    for ($bulan=1; $bulan<=12; $bulan++)
                    {
                        if ($bulan==$aBulan) $anoSulod.="       <option value='".$bulan."' selected>".$anoBulan[$bulan-1];
                        else $anoSulod.="       <option value='".$bulan."'>".$anoBulan[$bulan-1];
                    }
                    $anoSulod.="                    </select> ";

// Kanan year ini dinhi
                    $anoSulod.="                    <select name='bTuig'>";
                    for ($tuig=$tuigYana; $tuig>=$tuigHadto; $tuig--)
                    {
                        if ($tuig==$aTuig) $anoSulod.=" <option value='".$tuig."' selected>".$tuig;
                        else $anoSulod.="       <option value='".$tuig."'>".$tuig;
                    }
                    $anoSulod.="                    </select>";
                    $anoSulod.="            	";
                    $anoSulod.="
";
                    $anoSulod.="
Basic Salary : PhP	<input type='text' name='bs'>
";
                    $anoSulod.="
PERA/ACA : PhP	<input type='text' name='pa'>
";
                    $anoSulod.="
PAIC : PhP	<input type='text' name='pc'>
";
                    $anoSulod.="
REL : PhP	<input type='text' name='rl'>
";
                    $anoSulod.="
GSIS Premium/Loans : PhP	<input type='text' name='gs'>
";
                    $anoSulod.="
HDMF Premium/Loans : PhP	<input type='text' name='hd'>
";
                    $anoSulod.="
Coop Loans : PhP	<input type='text' name='cl'>
";
                    $anoSulod.="
Union Dues : PhP	<input type='text' name='ud'>
";
                    $anoSulod.="
Withholding Tax : PhP	<input type='text' name='tx'>
";
                    $anoSulod.="

";
                    $anoSulod.="
<input type='submit' value='Save'>
";
                    $anoSulod.="

";
                    $anoSulod.="
";
                    $anoSulod.="     ";
                    $anoSulod.="
";

                    $anoSulod.="
Payroll Details
";
                    $anoSulod.="
";
                    $anoSulod.="      	";
                    $anoSulod.="     Pay period	";
                    $anoSulod.="     Basic Salary	";
                    $anoSulod.="     PERA/ACA	";
                    $anoSulod.="     PAIC	";
                    $anoSulod.="     REL	";
                    $anoSulod.="     GSIS Premium/Loans	";
                    $anoSulod.="     HDMF Premium/Loans	";
                    $anoSulod.="     Coop Loans	";
                    $anoSulod.="     Union Dues	";
                    $anoSulod.="     Withholding Tax	";
                    $anoSulod.="
";

                    $k="select b.payID, a.empname, date_format(b.date,'%M %Y') as date, b.bs, b.pa, b.pc, b.rl, b.gs, b.hd, b.cl, b.ud, b.tx, from emp a, pay b where a.empID=b.empID and a.empID=".$empID;
                    $q=mysql_query($k);
                    if (mysql_num_rows($q)>0)
                    {
                        while ($akonRows=mysql_fetch_array($q))
                        {
                            $anoSulod.="
";
                            $anoSulod.="     <input type='radio' name='payID' value='".$akonRows["payID"]."'>	";
                            $anoSulod.="     ".$akonRows["date"]."	";
                            $anoSulod.="     ".$akonRows["bs"]."	";
                            $anoSulod.="     ".$akonRows["pa"]."	";
                            $anoSulod.="     ".$akonRows["pc"]."	";
                            $anoSulod.="     ".$akonRows["rl"]."	";
                            $anoSulod.="     ".$akonRows["gs"]."	";
                            $anoSulod.="     ".$akonRows["hd"]."	";
                            $anoSulod.="     ".$akonRows["cl"]."	";
                            $anoSulod.="     ".$akonRows["ud"]."	";
                            $anoSulod.="     ".$akonRows["tx"]."	";
                            $anoSulod.="
";
                        }
                    }
                    else
                    {
                        $anoSulod.="
";
                        $anoSulod.="     No records found	";
                        $anoSulod.="
";
                    }

                    $anoSulod.="
";

                    $anoSulod.="     	";
                    $anoSulod.="
";
                    $anoSulod.="
";
                    $anoSulod.="</form>";
                    $kadamoTD=2;
           }

      }
}

Please help. I really can't figure out what went wrong. Please do... Thanks so much!


问:
How to Set typeorm connection pool size in Nestjs? (MySQL)
I saw this link create connection pool TypeOrm

This article explained

```<code>"extra": {
  "connectionLimit": 5
}
</code>```

But I thought that this is just max limit of connection instead of creating connection pool in advance

How can I create typeorm(mysql) connection pool in Nestjs?

答:
From the words of one of the authors:

TypeORM creates a connection pool and uses connection for a single
  operation (find, save, remove, etc.) - it takes the connection, uses
  it and releases it. That's it. If all connections are busy it waits
  until any of them is released and then use it.

If you want your pool to have a max size, then extra.connectionLimit is the way to go.

Body:
I saw this link create connection pool TypeOrm

This article explained

"extra": {
  "connectionLimit": 5
}

But I thought that this is just max limit of connection instead of creating connection pool in advance

How can I create typeorm(mysql) connection pool in Nestjs?


问:
Microsoft Android application is not matching the signature hash with the application reply URI
Goal: Use the MS Identity application that was created by Microsoft and try to tailor the application according to our needs.

Issue The android application is not matching the signature hash with the Applications' Reply URI.

Configuration Values

```<code>Signature Hash: ga0RGNYHvNM5d0SLGQfpQWAPGJ8=
Redirect URI: msauth://com.azuresamples.msalandroidapp/ga0RGNYHvNM5d0SLGQfpQWAPGJ8%3D
Android:host: com.azuresamples.msalandroidapp
Android Scheme: msauth
</code>```

Logs and Screenshots

Logs show: The redirect URI in the configuration file doesn't match with the one generated with package name and signature hash.
Screenshot of the error in the Android Emulator

Methods Tried

Tried to fix this by changing the android:path value to /ga0RGNYHvNM5d0SLGQfpQWAPGJ8%3D however got this message from the UI:
Screenshot of the update android:path value

Tried to change the Reply URI directly from Azure Portal, but was not able to

Tried changing the Redirect URI in the android code to msauth://com.azuresamples.msalandroidapp/ga0RGNYHvNM5d0SLGQfpQWAPGJ8%3D but that gave the same error

The default library in the code was 1.2, changed it to 1.5+ in gradle and still it didnt work.

Configuration Files

auth_config_single_account.json

```<code>
  "client_id" : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "authorization_user_agent" : "DEFAULT",
  "redirect_uri" : "msauth://com.azuresamples.msalandroidapp/ga0RGNYHvNM5d0SLGQfpQWAPGJ8%3D",
  "account_mode" : "SINGLE",
  "broker_redirect_uri_registered": true,
  "authorities" : [
    {
      "type": "AAD",
      "authority_url": "https://login.microsoftonline.com/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    }
  ]
}
</code>```

AndroidManifest.xml

```<code><?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.azuresamples.msalandroidapp">
    ...
            <intent-filter>
                ...
                <data
                    android:host="com.azuresamples.msalandroidapp"
                    android:path="/ga0RGNYHvNM5d0SLGQfpQWAPGJ8="
                    android:scheme="msauth" />
            </intent-filter>
       ...

</manifest>
</code>```

Build-Gradle:app

```<code>...
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "androidx.appcompat:appcompat:$rootProject.ext.appCompatVersion"
    implementation "com.google.android.material:material:$rootProject.ext.materialVersion"
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'

    implementation 'com.android.volley:volley:1.1.1'

    if (findProject(':msal') != null) {
        // For developer team only.
        localImplementation project(':msal')
        externalImplementation 'com.microsoft.identity.client:msal:1.5.+'
    }
    else {
        // Downloads and Builds MSAL from maven central.
        implementation 'com.microsoft.identity.client:msal:1.5.+'
    }

}
</code>```

Update

Was trying to avoid this but I have updated my keystore using the debug.keystore that microsoft provides and it works, however this is not solution for production environments. What are the steps I need to take for this to work in the production environment.

答:
This is Dome from the MSAL Android team.

If you want to use your own keystore, you will have to register your own application.

com.azuresamples.msalandroidapp was only registered with the signature hash of the provided debug.keystore.

Please let me know if you have any other question. You can also leave a question in our GitHub repository as well (we're constantly monitoring that one).

Body:
Goal: Use the MS Identity application that was created by Microsoft and try to tailor the application according to our needs.

Issue The android application is not matching the signature hash with the Applications' Reply URI.

Configuration Values

Signature Hash: ga0RGNYHvNM5d0SLGQfpQWAPGJ8=
Redirect URI: msauth://com.azuresamples.msalandroidapp/ga0RGNYHvNM5d0SLGQfpQWAPGJ8%3D
Android:host: com.azuresamples.msalandroidapp
Android Scheme: msauth

Logs and Screenshots

Logs show: The redirect URI in the configuration file doesn't match with the one generated with package name and signature hash.
Screenshot of the error in the Android Emulator

Methods Tried

Tried to fix this by changing the android:path value to /ga0RGNYHvNM5d0SLGQfpQWAPGJ8%3D however got this message from the UI:
Screenshot of the update android:path value

Tried to change the Reply URI directly from Azure Portal, but was not able to

Tried changing the Redirect URI in the android code to msauth://com.azuresamples.msalandroidapp/ga0RGNYHvNM5d0SLGQfpQWAPGJ8%3D but that gave the same error

The default library in the code was 1.2, changed it to 1.5+ in gradle and still it didnt work.

Configuration Files

auth_config_single_account.json

  "client_id" : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "authorization_user_agent" : "DEFAULT",
  "redirect_uri" : "msauth://com.azuresamples.msalandroidapp/ga0RGNYHvNM5d0SLGQfpQWAPGJ8%3D",
  "account_mode" : "SINGLE",
  "broker_redirect_uri_registered": true,
  "authorities" : [
    {
      "type": "AAD",
      "authority_url": "https://login.microsoftonline.com/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    }
  ]
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.azuresamples.msalandroidapp">
    ...
            <intent-filter>
                ...
                <data
                    android:host="com.azuresamples.msalandroidapp"
                    android:path="/ga0RGNYHvNM5d0SLGQfpQWAPGJ8="
                    android:scheme="msauth" />
            </intent-filter>
       ...

</manifest>

Build-Gradle:app

...
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "androidx.appcompat:appcompat:$rootProject.ext.appCompatVersion"
    implementation "com.google.android.material:material:$rootProject.ext.materialVersion"
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'

    implementation 'com.android.volley:volley:1.1.1'

    if (findProject(':msal') != null) {
        // For developer team only.
        localImplementation project(':msal')
        externalImplementation 'com.microsoft.identity.client:msal:1.5.+'
    }
    else {
        // Downloads and Builds MSAL from maven central.
        implementation 'com.microsoft.identity.client:msal:1.5.+'
    }

}

Update

Was trying to avoid this but I have updated my keystore using the debug.keystore that microsoft provides and it works, however this is not solution for production environments. What are the steps I need to take for this to work in the production environment.


问:
php split a file into $key=>$value pairs with duplicate keys
I've been trying to wrap my head around this and I'm just brain frozen.

I have a file with multiple lines, each line is delimited by "=" into separate pieces of data:

```<code>// Example file. Syntax: <key>=<Value>
key1=value1
key1=value2
key1=value3
key2=value1
key2=value2
key3=value1
key3=value2
key3=value3
key3=value4
</code>```

I need to explode this file into separate lines, and throw them all in an array such that key1 holds an array of value1, 2, 3, key2 has its values, key3 has its values, so on and so forth. Kind of like this:

```<code>Array
(
    [key1] => Array
        (
        [0] => value1
        [1] => value2
        [2] => value3
        )
    [key2] => Array
        (
        [0] => value1
        [1] => value2
        )
    [key3] => Array
        (
        [0] => value1
        [1] => value2
        [2] => value3
        [3] => value4
        )
)
</code>```

So far I have the following code:

```<code>$loadFile = file('file.txt');
$returnArray = array();
foreach ($loadFile as $line) {
  $startWith = substr(trim($line), 0, 2);
    if (empty($line) || $startWith == '//') {
          continue;
  }
  list($key, $value) = explode('=', $line);
  $returnArray[$key] = $value;
}
return $returnArray;
</code>```

However all I'm getting back is:

```<code>Array ( [key1] => value3 [key2] => value2 [key3] => value4 )
</code>```

I've also tried

```<code>$loadFile = file('file.txt');
$returnArray = array();
foreach ($loadFile as $line) {
  $startWith = substr(trim($line), 0, 2);
    if (empty($line) || $startWith == '//') {
          continue;
  }
  $exp = explode('=', $line);
  $returnArray[$exp[0]] = $exp[1];
}
return $returnArray;
</code>```

Which also gives me the same result. I've also tried an array_merge which just shoved everything into an array with a numeric value as the key.

答:
I think your problem is you override the value for your key.

```<code>$returnArray[$key] = $value;
</code>```

so what yo want is to append your values to a subarray with the $key as the parent.

```<code>$returnArray[$key][] = $value;
</code>```

With the [] you append the $value to the underlying array.

Body:
I've been trying to wrap my head around this and I'm just brain frozen.

I have a file with multiple lines, each line is delimited by "=" into separate pieces of data:

// Example file. Syntax: <key>=<Value>
key1=value1
key1=value2
key1=value3
key2=value1
key2=value2
key3=value1
key3=value2
key3=value3
key3=value4

I need to explode this file into separate lines, and throw them all in an array such that key1 holds an array of value1, 2, 3, key2 has its values, key3 has its values, so on and so forth. Kind of like this:

Array
(
    [key1] => Array
        (
        [0] => value1
        [1] => value2
        [2] => value3
        )
    [key2] => Array
        (
        [0] => value1
        [1] => value2
        )
    [key3] => Array
        (
        [0] => value1
        [1] => value2
        [2] => value3
        [3] => value4
        )
)

So far I have the following code:

$loadFile = file('file.txt');
$returnArray = array();
foreach ($loadFile as $line) {
  $startWith = substr(trim($line), 0, 2);
    if (empty($line) || $startWith == '//') {
          continue;
  }
  list($key, $value) = explode('=', $line);
  $returnArray[$key] = $value;
}
return $returnArray;

However all I'm getting back is:

Array ( [key1] => value3 [key2] => value2 [key3] => value4 )

I've also tried

$loadFile = file('file.txt');
$returnArray = array();
foreach ($loadFile as $line) {
  $startWith = substr(trim($line), 0, 2);
    if (empty($line) || $startWith == '//') {
          continue;
  }
  $exp = explode('=', $line);
  $returnArray[$exp[0]] = $exp[1];
}
return $returnArray;

Which also gives me the same result. I've also tried an array_merge which just shoved everything into an array with a numeric value as the key.

Body:
I think your problem is you override the value for your key.

$returnArray[$key] = $value;

so what yo want is to append your values to a subarray with the $key as the parent.

$returnArray[$key][] = $value;

With the [] you append the $value to the underlying array.