WordPress Community Support Program Recommends Cautious Spending Until In-Person Events Can Renew Revenue Stream

WordPress Community Support (WPCS), the subsidiary of the WordPress Foundation that handles financial and legal support for official WordPress events, has published an overview of the program’s current finances and a summary of 2020.

Harmony Romo, a financial wrangler at Automattic, reported that WPCS ended the year with approximately $1.2M of cash on hand. $293,000 of the total represents unused Global Sponsorship funds that were rolled over from 2020. Current estimated operating expenses are $276,000. The total cash also includes $216,374 in prepaid expenses that were allocated in 2020 for WordCamps Asia, Atlanta, and US.

Newly published WordPress Foundation financials for 2020 show the WPCS program ended with a net loss, which has qualified for a refund of $50,708.

The WordPress Foundation itself has a lean operating budget, with 2020 expenses totaling $3,438 and donations totaling $10,787. As the subsidiary handling events, WPCS ‘s primary expenses are Meetup.com fees, which total $55,000 per quarter or $220,000 per year. The program will be carefully auditing Meetup groups later in the year to remove those that are inactive, as WPCS is billed for each group.

The main takeaway from the recently published financials is that WPCS’ finances are heavily dependent on corporate sponsorships. With the exception of a handful of in-person WordCamps and meetups, WordPress events have largely been on ice since March 2020. Sponsorship revenue dries up when most events are held virtually, as these types of events tend to offer a less meaningful return for companies and WPCS cannot justify the same sponsorship tiers offered in previous years.

The proposal for the 2022 Global Community Sponsorship program did not include funding for WordCamps again this year, due to the unpredictability of hosting in-person events. The program was also significantly pared back from multiple tiers (ranging from $40,000 – $160,000) to a single package billed at $10,000 USD per quarter, due to lower volunteer engagement among those who administrate sponsorships.

“WPCS does have an adequate amount of cash on hand to meet the current needs of the program, so as we proceed, we should do so cautiously until there is a solid return to in-person events and thus a reliable revenue stream,” Romo said. “Responsible expense decisions have allowed the program to endure and stay flexible as in-person events slowly return from hiatus.”

There are six upcoming in-person WordCamps are on the schedule for 2022 so far. In the absence of Global Sponsorship Program funding for WordCamps, organizers have been told they need to be prepared to raise 100% of the funds for their events. While I don’t think lack of finances is fueling the controversial drive to restart in-person events while pandemic deaths are still high in many areas of the world, getting camps on the books for 2022 is undoubtedly critical to recapturing corporate sponsorship funds. The Global Sponsorship Program changes that were proposed in November 2021 are currently being finalized and the 2022 version of the program should be published soon.

Block Editor Sidebar Panels Are the New Admin Notices

There is a problem. Well, it is not an OMGBBQ problem, but it has the potential to become one. Maybe by calling attention to it, I will set off a landslide of copycats who will see this as another trick of the marketing trade, implementing it in their own projects. I am torn, but it would be a disservice to our community to not provide a place for them to share their thoughts.

I am always looking for exciting new plugins or even those old ones that I have missed. In particular, I love to see what others are building on top of the block editor.

Several months ago, I activated my first plugin that used an editor sidebar panel to advertise its pro upgrade. There were no usable options. It was simply an ad, so the decision to deactivate and delete it was a no-brainer. I did not need anything taking up valuable real estate on the post-editing screen. I do not remember its name; it was on the site and off again in moments. A few months later, I saw another plugin with a similar panel.

I have simply ignored those extensions. They were somebody else’s issues. But, when the problem comes knocking at your own door, it is tough to not know it is there.

I hate to be the bad guy who calls out WordPress businesses for trying to make a buck. I have been there and tried to walk the tightrope between putting food on the table and creating a positive user experience with my products — said the guy who is now employed as a writer.

However, can we not do this?

WordPress editor open.  On the right, the post sidebar is shown with the ExactMetrics plugin's panel highlighted.  It has disabled options and simply an upgrade to pro link.
Sidebar panel with disabled options and a pro upgrade ad.

This panel seemed to appear suddenly on the Tavern’s post editor not long ago, and it has been an annoyance ever since. I dug around to find that this was a new pro option added to the ExactMetrics plugin last month. Users of the “lite” version just get the panel — free of charge.

There are a few choices when faced with such a situation. Learn to live with it, deactivate the plugin, or disable the panel via the preferences menu.

WordPress editor preferences menu overlay modal.  The Panels tab is selected.
Preference menu overlay.

At least the editor has some built-in noise control. I am not sure how many users are even aware that it is possible because it is almost hidden. It takes three clicks to get there (Options > Preferences > Panels) and another click to switch a panel off.

The JavaScript for the panel still runs on every load of the editor. And, clearing local browser storage means it will reappear — I will blame that one on WordPress for not storing preferences via user meta. But, at least panels can be hidden.

WordPress users have enough noise with plugins shouting at them at every turn. It is easy for them to become desensitized to the barrage of admin notices, a part of their daily existence. I do not even click the dismiss button for some at first glance. I let them sit, untouched, wondering if they will simply disappear into the ether without my direct interaction. Other times, I install Toolbelt and let it tuck them away.

But, this new thing? The post editor was a place of solace, an escape from the commotion allowed through the admin notices hook elsewhere. It was a quiet room for focusing on content.

At the very least, this form of advertising has given me the necessary kick in the pants to perform a full audit of the Tavern’s plugins. We are in the process of cleaning house, and I have already tossed the first into the trash heap.

15+ WordPress Features You Didn’t Know About

WordPress Features You Didn't Know AboutWordPress is the most powerful content management system globally, used by millions of people every day to create outstanding websites on command. It’s a feature-packed, constantly-evolving platform that anyone can use to present ideas awesomely. However, many of those features are entirely unknown to most users despite their robust use cases. In this article, we’ll […]

The post 15+ WordPress Features You Didn’t Know About appeared first on WPExplorer.

Android Native – Typesafe Navigation with Args

Introduction

When using Navigation Components, you might have wondered how to pass arguments between destinations. We will learn how to do just that in this tutorial. This tutorial also builds upon the Basic Navigation tutorial, so if you are not familiar with Navigation Components, I would recommend you to check it out.

There are two parts to this tutorial. First, we will learn how to perform navigation with type safety. Secondly, we will learn how to pass arguments with Safe Args.

Goals

At the end of the tutorial, you would have learned:

  1. How to use Navigation with Type Safety.
  2. How to pass arguments between destinations with Safe Args.
Tools Required
  1. Android Studio. The version used in this tutorial is Bumblebee 2021.1.1 Patch 1.
Prerequisite Knowledge
  1. Basic Android.
  2. Basic Navigation.
Project Setup

To follow along with the tutorial, perform the steps below:

  1. Create a new Android project with the default Empty Activity.

  2. Remove the default Hello World! TextView.

  3. In the Project-level build.gradle file, add the buildscript{} block below. Make sure that you place it above the existing plugins{} block.

     buildscript {
        repositories {
            google()
        }
        dependencies {
            def nav_version = "2.4.0"
            classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
        }
     }
  4. In the Module-level build.gradle file, add the dependencies below to the dependencies{} block.

     def nav_version = "2.4.0"
    
     implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
     implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
  5. Also in the Module-level build.gradle file, add the plugin below into the plugins{} block.

     id 'androidx.navigation.safeargs.kotlin'
  6. Because of this bug, you will have to downgrade your Android Gradle Plugin (AGP) to version 7.0.4. Gradle version 7.4 seems to work fine as of this tutorial. You can change your Project settings by going to File > Project Structure > Project.
    gradle_version.jpg

  7. Performs a Gradle sync.

  8. In the same package as MainActivity.kt, create two new fragments called BlankFragment1.kt, BlankFragment2.kt along with their respective layout files fragment_blank1.xml and fragment_blank2.xml.

  9. Create a new navigation graph called nav_graph.xml.

  10. Replace the content of activity_main.xml with the code below.

     <?xml version="1.0" encoding="utf-8"?>
     <androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <androidx.fragment.app.FragmentContainerView
            android:id="@+id/fragmentContainerView"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:defaultNavHost="true"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:navGraph="@navigation/nav_graph" />
     </androidx.constraintlayout.widget.ConstraintLayout>
  11. Replace the content of nav_graph.xml with the code below.

     <?xml version="1.0" encoding="utf-8"?>
     <navigation xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/nav_graph"
        app:startDestination="@id/blankFragment1">
    
        <fragment
            android:id="@+id/blankFragment1"
            android:name="com.codelab.daniwebandroidtypesafenav.BlankFragment1"
            android:label="fragment_blank1"
            tools:layout="@layout/fragment_blank1" >
            <action
                android:id="@+id/action_blankFragment1_to_blankFragment2"
                app:destination="@id/blankFragment2" />
        </fragment>
        <fragment
            android:id="@+id/blankFragment2"
            android:name="com.codelab.daniwebandroidtypesafenav.BlankFragment2"
            android:label="fragment_blank2"
            tools:layout="@layout/fragment_blank2" />
     </navigation>
  12. Add the string resource below into strings.xml.

     <string name="second_screen">2nd Screen</string>
  13. Replace the content of fragment_blank1.xml with the code below.

     <?xml version="1.0" encoding="utf-8"?>
     <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/frameLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".BlankFragment1" >
    
        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/second_screen"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
     </androidx.constraintlayout.widget.ConstraintLayout>
Typesafe Navigation

Before diving into navigation type safety, we will first look at what navigation without type safety is like. Copy and paste the overridden version of onViewCreated() below into BlankFragment1.

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   super.onViewCreated(view, savedInstanceState)

   val navController = findNavController()

   val button = view.findViewById<Button>(R.id.button)

   button.setOnClickListener {
       //Will compile, but will crash at runtime. No Compiler checks (except for @IdRes).
       navController.navigate(R.id.button)

       //Will compile. Won't crash at runtime. No Compiler checks (except for @IdRes).
       //navController.navigate(R.id.action_blankFragment1_to_blankFragment2)

       //Will compile. Won't crash at runtime. Has Compiler checks
       //val direction = BlankFragment1Directions.actionBlankFragment1ToBlankFragment2()
       //navController.navigate(direction)
   }
}

The code snippet above presents three different ways to navigate to a destination using a NavController. Without type safety, we normally can navigate using the android:id of the navigation <action>. When dissecting the method signature of the NavController#navigate(),

public open fun navigate(@IdRes resId: Int)

then we can see that it will take just about any Int argument. We can pass a useful Int value from R.id, or any number, really. There is an annotation @IdRes applied to the parameter which helps a bit due to the IDEs annotation processor. If you were to provide a non-id number to navigate(), you will see some help from the IDE in the form of a compiler error, which can be suppressed.

navigate_9999.jpg

Right now, we are passing to navigate() a valid R.id.button,

navController.navigate(R.id.button)

but it is not a valid navigation <action>, so we will not see any compiler error. The code will compile, but it will crash at runtime when we press the Button.

java.lang.IllegalArgumentException: Navigation action/destination com.codelab.daniwebandroidtypesafenav:id/button cannot be found from the current destination 

NavCrash.gif

The second way to navigate using the <action> android:id is correct and will not crash, but it is still not typesafe.

navController.navigate(R.id.action_blankFragment1_to_blankFragment2)

Because we have added the safe-args dependencies into our project, we can now use navigation with type safety. Type safety is provided by the interface NavDirections. The safe-args dependencies will generate a class implementing NavDirections at development time for each originating Destination of an <action>. This class will have the same name as the originating fragment, but with the word Directions suffixed to it. This new class will also have methods with the same name as the <action> android:id, converted to camel case after stripping out underscores (_).

compiler.jpg

You can now comment out the first two ways to navigate and uncomment the last way to navigate with NavDirections.

Pass data with Safe Args

In this section, we will learn how to pass arguments with Safe Args.

Replace the content of fragment_blank2.xml with the code below.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/frameLayout2"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".BlankFragment2">

   <TextView
       android:id="@+id/textView"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:textSize="32sp"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       tools:text="Test Test Test" />

</androidx.constraintlayout.widget.ConstraintLayout>

To pass an argument to blankFragment2 when navigating to it from blankFragment1, we will have to do a couple of things.

  1. We have to add an <argument> element to the destination fragment (blankFragment2). This will trigger the compiler to add a parameter to the method actionBlankFragment1ToBlankFragment2(), so it will become actionBlankFragment1ToBlankFragment2(sampleArgs: String = default value) (if <argument> has the attribute android:defaultValue). In nav_graph.xml, inside of blankFragment2, add the <argument> below.

     <argument
        android:name="sampleArg"
        app:argType="string"
        android:defaultValue="blank" />
  2. You might have to rebuild the project for the compiler to generate the new class.

  3. Back in the Buttons onClickListener callback, we can replace the previous direction with the code below to pass an argument.

     val direction = BlankFragment1Directions.actionBlankFragment1ToBlankFragment2("Arg from fragment1 to fragment2")
  4. In order for BlankFragment2 to receive the arguments, we can use the navArgs() delegate from androidx.navigation.fragment to receive a type of BlankFragment2Args, which the compiler has generated for us as well. In BlankFragment2.kt, copy and paste the code below.

     val args: BlankFragment2Args by navArgs()
    
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
    
        val textView = view.findViewById<TextView>(R.id.textView)
        textView.text = args.sampleArg
     }

And that is the last step in this tutorial.

Run the App

We are not ready to run the App. Your app should behave similarly to the Gif below.

NavSafeArgs.gif

Solution Code

BlankFragment1.kt

package com.codelab.daniwebandroidtypesafenav

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.navigation.fragment.findNavController

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"

/**
* A simple [Fragment] subclass.
* Use the [BlankFragment1.newInstance] factory method to
* create an instance of this fragment.
*/
class BlankFragment1 : Fragment() {
   // TODO: Rename and change types of parameters
   private var param1: String? = null
   private var param2: String? = null

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       arguments?.let {
           param1 = it.getString(ARG_PARAM1)
           param2 = it.getString(ARG_PARAM2)
       }
   }

   override fun onCreateView(
       inflater: LayoutInflater, container: ViewGroup?,
       savedInstanceState: Bundle?
   ): View? {
       // Inflate the layout for this fragment
       return inflater.inflate(R.layout.fragment_blank1, container, false)
   }

   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
       super.onViewCreated(view, savedInstanceState)

       val navController = findNavController()

       val button = view.findViewById<Button>(R.id.button)

       button.setOnClickListener {
           //Will compile, but will crash at runtime. No Compiler checks (except for @IdRes).
           //navController.navigate(R.id.button)

           //Will compile. Won't crash at runtime. No Compiler checks (except for @IdRes).
           //navController.navigate(R.id.action_blankFragment1_to_blankFragment2)

           //Will compile. Won't crash at runtime. Has Compiler checks
           //val direction = BlankFragment1Directions.actionBlankFragment1ToBlankFragment2()
           val direction = BlankFragment1Directions.actionBlankFragment1ToBlankFragment2("Arg from fragment1 to fragment2")
           navController.navigate(direction)
       }
   }

   companion object {
       /**
        * Use this factory method to create a new instance of
        * this fragment using the provided parameters.
        *
        * @param param1 Parameter 1.
        * @param param2 Parameter 2.
        * @return A new instance of fragment BlankFragment1.
        */
       // TODO: Rename and change types and number of parameters
       @JvmStatic
       fun newInstance(param1: String, param2: String) =
           BlankFragment1().apply {
               arguments = Bundle().apply {
                   putString(ARG_PARAM1, param1)
                   putString(ARG_PARAM2, param2)
               }
           }
   }
}

BlankFragment2.kt

package com.codelab.daniwebandroidtypesafenav

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.navigation.fragment.navArgs

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"

/**
* A simple [Fragment] subclass.
* Use the [BlankFragment2.newInstance] factory method to
* create an instance of this fragment.
*/
class BlankFragment2 : Fragment() {
   // TODO: Rename and change types of parameters
   private var param1: String? = null
   private var param2: String? = null

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       arguments?.let {
           param1 = it.getString(ARG_PARAM1)
           param2 = it.getString(ARG_PARAM2)
       }
   }

   override fun onCreateView(
       inflater: LayoutInflater, container: ViewGroup?,
       savedInstanceState: Bundle?
   ): View? {
       // Inflate the layout for this fragment
       return inflater.inflate(R.layout.fragment_blank2, container, false)
   }

   val args: BlankFragment2Args by navArgs()

   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
       super.onViewCreated(view, savedInstanceState)

       val textView = view.findViewById<TextView>(R.id.textView)
       textView.text = args.sampleArg
   }

   companion object {
       /**
        * Use this factory method to create a new instance of
        * this fragment using the provided parameters.
        *
        * @param param1 Parameter 1.
        * @param param2 Parameter 2.
        * @return A new instance of fragment BlankFragment2.
        */
       // TODO: Rename and change types and number of parameters
       @JvmStatic
       fun newInstance(param1: String, param2: String) =
           BlankFragment2().apply {
               arguments = Bundle().apply {
                   putString(ARG_PARAM1, param1)
                   putString(ARG_PARAM2, param2)
               }
           }
   }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">

   <androidx.fragment.app.FragmentContainerView
       android:id="@+id/fragmentContainerView"
       android:name="androidx.navigation.fragment.NavHostFragment"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       app:defaultNavHost="true"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>

fragment_blank1.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/frameLayout"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".BlankFragment1" >

   <Button
       android:id="@+id/button"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="@string/second_screen"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

fragment_blank2.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/frameLayout2"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".BlankFragment2">

   <TextView
       android:id="@+id/textView"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:textSize="32sp"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       tools:text="Test Test Test" />

</androidx.constraintlayout.widget.ConstraintLayout>

nav_graph.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/nav_graph"
   app:startDestination="@id/blankFragment1">

   <fragment
       android:id="@+id/blankFragment1"
       android:name="com.codelab.daniwebandroidtypesafenav.BlankFragment1"
       android:label="fragment_blank1"
       tools:layout="@layout/fragment_blank1" >
       <action
           android:id="@+id/action_blankFragment1_to_blankFragment2"
           app:destination="@id/blankFragment2" />
   </fragment>
   <fragment
       android:id="@+id/blankFragment2"
       android:name="com.codelab.daniwebandroidtypesafenav.BlankFragment2"
       android:label="fragment_blank2"
       tools:layout="@layout/fragment_blank2" >
       <argument
           android:name="sampleArg"
           app:argType="string"
           android:defaultValue="blank" />
   </fragment>
</navigation>

strings.xml

<resources>
   <string name="app_name">Daniweb Android Typesafe Nav</string>
   <!-- TODO: Remove or change this placeholder text -->
   <string name="hello_blank_fragment">Hello blank fragment</string>
   <string name="second_screen">2nd Screen</string>
</resources>

Project build.gradle

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
   repositories {
       google()
   }
   dependencies {
       def nav_version = "2.4.0"
       classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
   }
}

plugins {
   id 'com.android.application' version '7.0.4' apply false
   id 'com.android.library' version '7.0.4' apply false
   id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
}

task clean(type: Delete) {
   delete rootProject.buildDir
}

Module build.gradle

plugins {
   id 'com.android.application'
   id 'org.jetbrains.kotlin.android'
   id 'androidx.navigation.safeargs.kotlin'
}

android {
   compileSdk 31

   defaultConfig {
       applicationId "com.codelab.daniwebandroidtypesafenav"
       minSdk 21
       targetSdk 31
       versionCode 1
       versionName "1.0"

       testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
   }

   buildTypes {
       release {
           minifyEnabled false
           proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
       }
   }
   compileOptions {
       sourceCompatibility JavaVersion.VERSION_1_8
       targetCompatibility JavaVersion.VERSION_1_8
   }
   kotlinOptions {
       jvmTarget = '1.8'
   }
}

dependencies {
   implementation 'androidx.legacy:legacy-support-v4:1.0.0'
   def nav_version = "2.4.0"

   implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
   implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

   implementation 'androidx.core:core-ktx:1.7.0'
   implementation 'androidx.appcompat:appcompat:1.4.1'
   implementation 'com.google.android.material:material:1.5.0'
   implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
   testImplementation 'junit:junit:4.13.2'
   androidTestImplementation 'androidx.test.ext:junit:1.1.3'
   androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
Summary

We have learned how to navigate with type safety and pass destination arguments in this tutorial. The full project code can be found at https://github.com/dmitrilc/DaniwebAndroidTypesafeNav.

WhiteSource Threat Report Documents JavaScript npm Cyberattacks

WhiteSource, an open-source security and management company, today released a new threat report based on malicious activity found in npm, the most popular JavaScript package manager used by developers worldwide. The report, Popular Javascript Package Registry Is a Playground For Malicious Actors, is based on findings from more than 1,300 malicious npm packages identified in 2021 by WhiteSource Diffend, the company's flagship automated malware detection platform.

How to Add Custom Code to WordPress

There are many books and tutorials that share useful code snippets for WordPress. For example, you can find hundreds of custom functions right here at DigWP.com. You can also find them in my WordPress books, tutorials, and code snippets. For many code snippets and custom functions, the usage instructions will say something like:

Add this code to your theme (or child theme’s) functions.php file, or add the code via simple custom plugin.

So what does that actually mean? Let’s take a closer look. First we’ll go through how to add custom code via the functions.php file. Then we’ll explain how to add code by making a simple custom plugin. Finally we’ll wrap things up by explaining the difference between the two methods and how to go further.

Contents

Add custom code via theme functions.php

Every WordPress theme can have a file named functions.php. If present in a theme, the functions file must be located in the root directory of the theme:

/wordpress/
	/wp-content/
		/themes/
			/my-theme/
				functions.php

If the functions file does not exist, create it. Then open the file in a code or text editor and add your custom code. Save the file, upload to the server, and done. It is very straightforward. Just make sure to test any new code on a private/test site before adding to a live production site.

Learn more about building themes in my book, WordPress Themes In Depth.

Add custom code via simple custom plugin

With WordPress, plugins add functionality, and themes display content. There is some overlap (and grey area), but in general the best way to add functionality to WordPress is with a plugin. That’s one reason why there are over 50,000 plugins in the WordPress Plugin Directory.

Plugins range in complexity. They can be very advanced, comprising many files and lots of code. Or they can be very simple, made with one file and a few lines of code. To add a custom code snippet, a simple plugin will suffice.

How to make a simple custom plugin

To make a simple custom plugin, follow these steps:

  1. Create a new PHP file
  2. Name the file whatever makes sense, can be any alphanumeric string (plus dashes and underscores if needed)
  3. Open the file and add the header code provided below
  4. Save the file and done

After creating this file, it is ready for your custom code snippet(s). To save you some time, I’ve created an example plugin that you can download below. It’s ready to go for any custom code that you want to add.

Download a simple custom plugin

Download a copy of our simple custom plugin, ready for your custom code.

Download Simple Custom Plugin (ZIP file < 1 KB)

Usage: Download and unzip the file. Open simple-custom-plugin.php and customize the file header as explained below. Then add your custom code snippet, save changes and done.

Plugin file header

At the beginning of your plugin file, add the following lines:

<?php 
/*
	Plugin Name: Simple Custom Plugin
	Plugin URI: https://digwp.com/2022/02/simple-custom-plugin/
	Description: This is a simple plugin template for adding custom code to WordPress.
	Author: Jeff Starr
	Author URI: https://plugin-planet.com/
	Requires at least: 5.9
	Tested up to: 5.9
	Version: 1.0
*/

if (!defined('ABSPATH')) die();

// add custom code snippets below this line..

You can customize the header lines with your actual information. As our simple plugin is meant only for your site and will not be distributed publicly, the file header can be much simpler than what’s required for plugins destined for the WP Plugin Directory. Learn more about plugin file headers at WordPress.org.

Also: notice this line:

if (!defined('ABSPATH')) die();

That line is included to prevent direct access to the file. It basically checks if WordPress is loaded; if not, the script simply exits. This is a basic security measure to help prevent anyone from meddling with your custom code.

Remember to use code snippets only from trusted sources. And then test the code on a private site before going live.

What’s the difference?

What’s the difference between adding code via theme functions vs. simple plugin? The main difference is scope. When code is added via your theme template, it will run only when the theme is active. So for example, say we add a custom code snippet that displays social media buttons on posts. If we change themes, the custom code will not run, and the buttons will not be displayed.

Contrast that with adding custom code via simple plugin. As long as the plugin is active, the custom code will run always, regardless of which theme you’re using. Going the plugin route also benefits in terms of things like extensibility and maintainability. Managing custom code via plugins generally is easier than burying it within the theme’s functions file.

So which is best? Neither. The two methods are just different. Which one is best for any given code snippet depends on various factors. Most importantly whether or not the custom code is theme specific or global in scope.

For an easy, no-fuss way to add custom code snippets, check out WPCodeBox.

Going further..

The above simple plugin example is the most basic possible. To go further with plugin development, visit the Plugin Developer Handbook at WordPress.org.

Check out my complete video course on developing WordPress plugins »

Questions and comments welcome! :)


10 Popular Augmented Reality APIs

Augmented Reality (AR) is technology that combines real world spaces or objects with virtual spaces or objects. The practice of interlaying virtual information over real world images has the potential to greatly enhance and improve lives, whether it be helping a consumer find better deals or helping a blind person navigate a building.

Mystifly Adds New API for Global Flight Data

Mystifly, a Singapore-based travel technology company, has announced a new API that is intended to expose global flight data for its enterprise customers. The new Mystifly Universe API takes advantage of the company’s broad network of carrier and itineraries options, helping to provide a low-cost, robust search tool. 

Rajeev Kumar, founder & CEO of Mystifly had this to say about the state of the industry and the value that is provided by this new API:

How can i Rank keywords?

Hi Team, Can you suggest me how to rank keywords on particular site. now i am doing seo for one website... i create lot of backlinks but till now i can't see any improvement why? suggest me guys

7 Best Gadgets for Freelance Graphic Designers

Freelance graphic designers like you create graphics, layouts, print pieces, and more for your clients. Your work may be for advertisements, websites, books, or other publications. With this type of job, you can’t carry out your duties without the help of computers and some cool gadgets. If you’re looking for the best gadgets, you’ll want...

The post 7 Best Gadgets for Freelance Graphic Designers appeared first on DesignrFix.

Gambling Casinonic Casino Online

  Contents Casinonic Casino Registration Have a fantastic gaming night, Casinonic Casino! In Casinonic Casino , what kinds of gaming options are available? Tips for Playing casinoplay Slots Online Since Casinonic Casino is such a well-known company, its name is likely familiar to many. This commercial appears in every medium imaginable, from online video to […]

How to Hide Unnecessary Menu Items From WordPress Admin

Do you ever wonder if it was possible to clean up the WordPress admin area for your users?

There are lots of things in the WordPress admin area that your users, authors, or clients don’t need to see or use. Cleaning up the admin area helps to keep them focused on only the options they need, without distracting clutter.

In this article, we will show you how to hide unnecessary items from WordPress admin.

How to hide items from WordPress admin

Why Hide Unnecessary Items for WordPress Admin?

In the WordPress admin area, there are a lot of menus, submenus, options and plugin settings that you can change anytime. Some of these menu items include dashboard widgets, post edit area, plugins, appearance, tools, and more.

However, most of these menus and settings are not used on a daily basis and they end up cluttering the admin area. If you run a multi-author website or have clients visiting the admin area, then it’s a good practice to clean up the WordPress admin panel.

You can only keep menus and options that are useful for your authors and clients, and hide the rest of the submenus.

You may also want to deactivate items based on user roles by creating different admin interfaces for users with different roles and capabilities on your WordPress site.

That said, let’s look at how you can remove and hide unnecessary items from WordPress admin.

Note: This guide is about the admin menu that a registered user on your site sees when they log in. If you’re wanting to customize the navigation menus that all your website visitors see, then you should see our beginner’s guide on navigation menus in WordPress.

Hiding Menu Items from WordPress Admin

The easiest way to hide menus and items from WordPress admin panel is by using the Admin Menu Editor plugin. It’s a free WordPress plugin that lets you change the menu titles, URLs, icons, and more.

You can also hide menu items from the admin area, set user role permissions, and drag and drop menu items to organize your WordPress admin.

First, you’ll need to install and active the Admin Menu Editor plugin. For more details, please see our guide on how to install a WordPress plugin.

Upon activation, you can head over to Settings » Menu Editor from your WordPress dashboard. Next, you’ll see all your menu and submenu items under the ‘Admin Menu’ tab.

Rearrange admin menu items

You can simply drag and drop your menu items to rearrange their order. There are also options to remove or add new menu items.

Next, go ahead and click the downwards arrow for any menu item to see more options. You can rename the menu title, change the target page, and more.

Change menu item settings

To hide a menu item for specific user, simply click the ‘Extra capability’ dropdown menu. After that, you can choose the user role who can view the menu item from the given options.

For example, let’s say you want to hide the Media menu and its submenus for all user roles except the administrator. To do that, simply click on the Extra capabilities dropdown menu and choose ‘Administrator’ under Roles.

Choose user role to hide menu item

Once you’re done, go ahead and save your changes.

Now the Media menu item will be visible to only the Administrator role and will be hidden to other user roles.

If someone still tries to access the hidden menu item by typing in the URL, then they’ll see the error message ‘You do not have sufficient permissions to access this admin page.’

Restricted error message

You can now repeat these steps for hiding other menu items and plugins from the WordPress admin menu for different users.

We hope this article helped you hide unnecessary items from WordPress admin area. You may also want to check out our guide on how to get a free SSL certificate in WordPress and the best WooCommerce plugins.

If you liked this article, then please subscribe to our YouTube Channel for WordPress video tutorials. You can also find us on Twitter and Facebook.

The post How to Hide Unnecessary Menu Items From WordPress Admin first appeared on WPBeginner.