Does WordPress Need 1,000s of Block Themes in the Era of Full Site Editing?

“I have so many design ideas in my head that I am about to make it my mission to singlehandedly fulfill [Matt Mullenweg’s] vision of 5,000 Full Site Editing #WordPress themes in the directory,” tweeted Brian Gardner earlier today.

Daniel Schutzsmith responded:

I’m just not seeing the need for more than one theme for FSE as long as I can override the look of it.

Would appreciate someone explaining why one theme like @frostwp COULDN’T just be the standard.

Making more themes feels like it defeats the concept of FSE altogether.

It is not the first time someone asked whether more than one block theme is necessary. In 2019, Rich Tabor proposed adding a base theme to WordPress itself, one upon which others would be built, if at all.

Even that was not the first time someone had pondered similar one-theme utopias throughout the platform’s history. Many framework-style parent themes have all made a run for it.

Let us assume for a moment that WordPress has reached a state where all themes no longer need custom PHP and CSS code. We are nowhere near that point yet, but we can imagine such a day might be possible in the relatively distant future. In this ideal world, templating, styling, theme-supported features, and plugin integration are neatly packaged into something configurable from the admin. In practice, users could control any aspect of their site’s front-end through the interface.

The problem is that someone still needs to make those customizations, and not everyone has a knack for design. One person’s ability does not automatically translate to all other users.

Perhaps a more crucial point is that not everyone wants to customize their site’s design. Some people simply want to find something that suits their style and move along.

There are alternative routes for arriving at the same destination, but themes are currently the only reliable vehicle.

Schutzsmith tweeted the following in response to Jamie Marsland, who likened the notion to asking Picasso for the canvas instead of the finished painting:

Themes and swapping out the whole thing is an old way of thinking. Sure a theme could = painting but I’m saying why can’t we just swap out the theme.json and get the same result? Why the need for themes at all when all we need to change is theme.json.

That is a future that I would not mind striving for. It is not insurmountable, but there is an uphill climb that WordPress will undoubtedly struggle with. Without a standardized CSS toolkit in place, switching theme.json files simply does not work. If WordPress tackles that problem, it takes us one step closer.

But theme.json only represents settings and styles. It says nothing about the structure of a website. Pre-configured templates are still necessary. Right now, that job sits squarely on top of the theme author’s shoulders.

If and when a well-designed user experience for full-page patterns lands in WordPress (related ticket), the template argument becomes less relevant. With such a system in place and enough variety from the pattern directory, some users might not require themes to handle this.

Starter templates or page patterns selector for inserting full-page content into the WordPress editor.
Starter page templates proposal.

The only worthwhile argument I have for multiple — even 1,000s of — themes is the promise they make to the user: install this thing, and you get this result.

For example, a pizzeria owner installs WordPress on their site and begins looking for a design for their online presence. This is likely someone working all day in a hot kitchen and comes home exhausted at night. However, they hop on the computer to update tomorrow’s specials or tinker with a new homepage layout for a few minutes. Everything about that experience should be catered to their use case. As an owner, chef, spouse, and parent, they need to quickly get things done and spend the rest of the night with their family.

This and 1,000s of similar scenarios are why themes are as important today as they ever were. Not everyone has the privilege of time, the skillset, or the inclination to piece their sites together.

When done well, block themes offer a controlled experience that cuts out all the cruft. They feel like they were built for an audience of one while being flexible enough for public release.

Schutzsmith later tweeted in the thread that he liked Elementor’s Kits. These are predesigned website designs that span multiple industries.

Pattern category types, which are not currently in WordPress, could evolve into that role. The Block Pattern Explorer plugin enables the feature, but themes must add support for the types to appear.

In the following screenshot, I have created a “Profile Cards” type in a theme of mine, but it could be industry-specific:

Block pattern explorer opened to a specific block pattern category type.
Block pattern category types.

It should be as easy as locating an industry-specific type and finding patterns for the pizzeria owner. A theme can offer that by either packaging patterns or pointing to those hosted in the directory.

I could see this evolving into more of a kit-like solution.

I disagree with Schutzsmith’s conclusion of needing only a single theme but not the questions he is asking. Our design community cannot simply say that themes should be “this one thing” because that is what they have always been. Its members need to continually ask: What is a WordPress theme?

The answer may be different to various groups of creators and users. If someone can get everything they need from the pattern directory without switching from Twenty Twenty-Two, maybe themes are irrelevant. If a creator simply likes building global style variations (theme.json files), WordPress should make it easy to use them on a wide range of sites.

However, many users will still need turnkey design solutions, and themes can be the best way to facilitate that. I do not know if that is 100, 1,000 or 5,000, but we will see how it goes.

Koji Launches New Block Plugin for WordPress

Koji, a popular “link in bio” platform that provides apps to the creator economy, has launched a WordPress plugin for the block editor. The San Diego-based startup has raised $36.1M, most recently securing $20M in a funding round led by Jump Capital. Koji has attracted 150,000 users since its official launch in March 2021. It offers a way for creators on TikTok, Instagram, Twitch, and other social media platforms to monetize their followings.

“WordPress, along with the bloggers and creators it supports, is a cornerstone of the modern creator economy,” Koji CEO Dmitry Shapiro said. “This integration is a massive addition to the power and extensibility of WordPress, giving bloggers a frictionless and intuitive way to incorporate Koji mini apps in their content strategies.”

The new plugin introduces a Koji App block that gives users access to Koji’s app store and 200+ web-based mini apps. The apps offer a wide array of interactive functionality, such as e-commerce, games, video guestbook, calendar scheduling, NFT storefronts, billboards, and more.

Koji App Store in the Block Editor

Although the plugin’s description page says that it allows users to “embed any Koji app,” it’s not explicit about how that looks on the frontend. The app store launches in an overlay where you select and customize the app. In testing the plugin, I found that when when you insert the app into your WordPress site, it just shows up as a black button that says “My Koji App.” You can customize the button’s color and text, but that’s about it. The button launches the app in a lightbox-style overlay on top of the page content.

Koji App display on the frontend

The image above is a screenshot of two buttons that Koji inserted representing a Tip Jar app and an Ask Me Anything app. There is no way to differentiate the two besides editing the text.

The screenshots on the plugin’s details page show how the app is launched over the content, but it doesn’t show what users see on the page before they click the link.

I was not particularly impressed with this first version of the plugin, as it seems to misrepresent what it actually does. The plugin places links in plain black buttons that will launch an iframe of the Koji app on the page. It doesn’t make much use of the block editor’s customization features, nor does it provide any kind of visual preview of the app on the page. Even a small thumbnail preview would be far superior to a plain black button.

The plugin says that embedding these apps allows users to “sell downloadable files, sell custom video requests like shoutouts and birthday greetings, embed games that can be played by visitors, and more.” It does make it possible for users to do these things in the iframe popup, but the plugin doesn’t provide any kind of preview for these apps that would compel a visitor to click on them.

The Koji Block plugin offers a nominal integration between the Koji platform and WordPress, but misses an opportunity to display visually appealing previews within content. For an app platform with hundreds of colorful, interactive apps, the plain black button method of displaying them is uninspiring.

Koji collects fees on apps with e-commerce capabilities. If you are looking to collect donations or sell products on your WordPress site, there are many more homegrown (and visually appealing) ways to do so. This plugin is best-suited to creators who are already heavily using the Koji platform and want a simple way to link to an app from WordPress.

Android Native – How to add Foreign Keys to Room entities

Introduction

When working with Room, there might come a situation during bulk CRUD operations that it would be useful to add a Foreign Key to one of our tables. This provides two main benefits: cascading delete (must be configured manually) and enforcing data integrity on linked entities.

In this tutorial, we will learn how to add Foreign Keys into Room entities.

Goals

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

  1. How to add Foreign Keys to Room entities.
Tools Required
  1. Android Studio. The version used in this tutorial is Bumblebee 2021.1.1 Patch 2.
Prerequisite Knowledge
  1. Intermediate Android.
  2. SQL.
  3. Basic Room database.
  4. Kotlin coroutines.
Project Setup

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

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

  2. Add the dependencies below for Room into the Module build.gradle.

     def room_version = "2.4.2"
     implementation "androidx.room:room-runtime:$room_version"
     kapt "androidx.room:room-compiler:$room_version"
     implementation "androidx.room:room-ktx:$room_version"
     implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
  3. In the same file, add the kapt plugin under plugins

     id 'kotlin-kapt'
  4. Create a new @Entity called Student with the code below.

     @Entity
     data class Student(
        @PrimaryKey(autoGenerate = true) val id: Long = 0,
        @ColumnInfo(name = "first_name") val firstName: String,
        @ColumnInfo(name = "last_name") val lastName: String
     )
  5. Create another @Entity called ReportCard with the code below.

     @Entity(tableName = "report_card")
     data class ReportCard(
        @PrimaryKey(autoGenerate = true) val id: Long = 0,
        @ColumnInfo(name = "student_id") val studentId: Long
     )
  6. Create a new empty DAO for the Student entity.

     @Dao
     interface StudentDao {
    
        @Insert(onConflict = OnConflictStrategy.IGNORE)
        suspend fun insertStudent(student: Student): Long
    
        @Query("SELECT * FROM student WHERE id=:id")
        suspend fun getStudentById(id: Long): Student?
    
     }
  7. Create the abstract class MyRoomDB with the code below.

     @Database(entities = [Student::class, ReportCard::class], version = 1)
     abstract class MyRoomDB : RoomDatabase() {
        abstract fun studentDao(): StudentDao
     }
  8. Append the code below to MainActivity onCreate(). This creates an instance of the database and then attempts to perform a query on the empty database.

     val db = Room.databaseBuilder(
        applicationContext,
        MyRoomDB::class.java, "my-room-db"
     ).build()
    
     lifecycleScope.launch {
        db.studentDao().getStudentById(1)
     }
  9. For most of the tutorial, we will interact with the database via the Database Inspector, and not in code. The DAO query above performs a dummy connection so that the Database Inspector can keep the database connection open in debugging mode. Run the app in Debug mode and then switch to the App Inspection window. Verify that the database connection stays open. Later on, we can interact with the database directly using a Query tab.

1.png

Project Overview

For this tutorial, we are completely ignoring the frontend. We will only focus on the interaction with the database via Room.

We currently have two entities, Student and ReportCard. The Student entity is not aware of the ReportCard entity, but the ReportCard entity is dependent on the Student entity. Each ReportCard contains its own ID as well as the associated Student ID.

There is no foreign key constraint to Student in ReportCard, so it is possible that a ReportCard might be referencing a Student who does not exist in the student table. At the end of the tutorial, we should have created a Foreign Key for the ReportCard entity; this way we can ensure that each ReportCard can only reference a valid Student.

The Problem

First, in order to have a better understanding of the problems that a Foreign Key can solve, let us walk through an example where data integrity is violated.

The current student table is empty, but we can INSERT new ReportCard into report_card just fine, referencing non-existent students.

INSERT INTO report_card (id, student_id) VALUES
(1, 30),
(2, 2)

2.png

If we query the join for the two tables, we would receive nothing because those student_id do not exist in the student table.

SELECT * FROM report_card INNER JOIN student ON report_card.student_id=student.id

3.png

Foreign Key

Fortunately for us, we can create a Foreign Key to an entity so that the database can throw errors when we try to violate this constraint. One thing to keep in mind is that this does not prevent developers from writing code that violates the constraint; only via runtime testing that data inconsistencies will show up with a SQL exception.

To apply a Foreign Key, we can simply pass in ForeignKey objects to Entitys foreignKey parameter. entity, childColumns, and parentColumns are required by ForeignKey.

@Entity(tableName = "report_card",
   foreignKeys = [ForeignKey(
       entity = Student::class,
       childColumns = ["student_id"],
       parentColumns = ["id"]
)])
data class ReportCard(
   @PrimaryKey(autoGenerate = true) val id: Long = 0,
   @ColumnInfo(name = "student_id") val studentId: Long
)

After adding the Foreign Key, you can re-install the App so Room can make use of the new constraint.

If we attempt to run the same INSERT into report_card again, Room will not allow us and throw an exception instead.

E/SQLiteQuery: exception: FOREIGN KEY constraint failed (code 787 SQLITE_CONSTRAINT_FOREIGNKEY); query: INSERT INTO report_card (id, student_id) VALUES
    (1, 30),
     (2, 2)

In order for the INSERT into report_card to work, valid students must exist. INSERT new Students with the statement below.

INSERT INTO student VALUES
(2, "Mary", "Anne"),
(30, "John", "Doe")

After this, you can INSERT into report_card for the valid student IDs.

INSERT INTO report_card (id, student_id) VALUES
(1, 30),
(2, 2)

If we run the JOIN query again, we can see that it returns all of the data correctly.

4.png

Solution Code

MainActivity.kt

package com.codelab.daniwebandroidroomforeignkey

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import androidx.room.Room
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)

       val db = Room.databaseBuilder(
           applicationContext,
           MyRoomDB::class.java, "my-room-db"
       ).build()

       lifecycleScope.launch {
           db.studentDao().getStudentById(1)
       }

   }
}

MyRoomDB.kt

package com.codelab.daniwebandroidroomforeignkey

import androidx.room.Database
import androidx.room.RoomDatabase

@Database(entities = [Student::class, ReportCard::class], version = 1)
abstract class MyRoomDB : RoomDatabase() {
   abstract fun studentDao(): StudentDao
   abstract fun reportCardDao(): ReportCardDao
}

ReportCard.kt

package com.codelab.daniwebandroidroomforeignkey

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey

@Entity(tableName = "report_card",
   foreignKeys = [ForeignKey(
       entity = Student::class,
       childColumns = ["student_id"],
       parentColumns = ["id"]
)])
data class ReportCard(
   @PrimaryKey(autoGenerate = true) val id: Long = 0,
   @ColumnInfo(name = "student_id") val studentId: Long
)

Student.kt

package com.codelab.daniwebandroidroomforeignkey

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "student")
data class Student(
   @PrimaryKey(autoGenerate = true) val id: Long = 0,
   @ColumnInfo(name = "first_name") val firstName: String,
   @ColumnInfo(name = "last_name") val lastName: String
)

StudentDao.kt

package com.codelab.daniwebandroidroomforeignkey

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query

@Dao
interface StudentDao {

   @Insert(onConflict = OnConflictStrategy.IGNORE)
   suspend fun insertStudent(student: Student): Long

   @Query("SELECT * FROM student WHERE id=:id")
   suspend fun getStudentById(id: Long): Student?

}

Module build.gradle

plugins {
   id 'com.android.application'
   id 'org.jetbrains.kotlin.android'
   id 'kotlin-kapt'
}

android {
   compileSdk 32

   defaultConfig {
       applicationId "com.codelab.daniwebandroidroomforeignkey"
       minSdk 21
       targetSdk 32
       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 {
   //Room deps
   def room_version = "2.4.2"
   implementation "androidx.room:room-runtime:$room_version"
   kapt "androidx.room:room-compiler:$room_version"
   implementation "androidx.room:room-ktx:$room_version"
   implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'

   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 create Foreign Keys on Room Entities. The full project code can be found at https://github.com/dmitrilc/DaniwebAndroidRoomForeignKey

Android Native – How to prepopulate a Room database

Introduction

If your Android Application uses Room, then you might have wondered how to prepopulate a Room database. In this tutorial, we will learn how to prepopulate a Room database.

Goals

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

  1. How to prepopulate a Room database.
Tools Required
  1. Android Studio. The version used in this tutorial is Bumblebee 2021.1.1 Patch 2.
Prerequisite Knowledge
  1. Basic Android.
  2. Basic Room.
Project Setup

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

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

  2. Add the dependencies below for Room into the Module build.gradle.

     def room_version = "2.4.2"
     implementation "androidx.room:room-runtime:$room_version"
     kapt "androidx.room:room-compiler:$room_version"
     implementation "androidx.room:room-ktx:$room_version"
     implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
  3. In the same file, add the kapt plugin under plugins.

     id 'kotlin-kapt'
  4. Add a new Person Entity with the code below.

     @Entity(tableName = "person")
     data class Person(
        @PrimaryKey(autoGenerate = true) val id: Long,
        @ColumnInfo(name = "first_name") val firstName: String,
        @ColumnInfo(name = "last_name") val lastName: String
     )
  5. Add a new Person DAO with the code below.

     @Dao
     interface PersonDao {
        @Query("SELECT * FROM person WHERE id=:id")
        suspend fun getPersonById(id: Long): Person?
     }
  6. Create a new Room database with the code below.

     @Database(entities = [Person::class], version = 1)
     abstract class MyDatabase : RoomDatabase(){
        abstract fun personDao(): PersonDao
     }
  7. In MainActivity#onCreate(), append the code below. This creates a dummy connection to the database at launch so that we can have access to it in debug mode.

     val db = Room.databaseBuilder(
        applicationContext,
        MyDatabase::class.java, "my-database"
     ).build()
    
     lifecycleScope.launch {
        db.personDao().getPersonById(1)
     }
Project Overview

For this tutorial, we are not concerned about the UI at all and only focus on the Room database itself.

There is only one simple Student Entity in our App. At the end of the tutorial, we should be able to prepopulate our database with data on boot.

Options for prepopulating data

There are three different ways to prepopulate a database.

  1. The first option involves using prepackaged database files located under a special directory called assets/.
  2. The second option allows you to load the prepackaged files from the file system itself.
  3. The third option includes loading the database files from an InputStream.

Because loading pre-packaged databases are all the same conceptually for all three options, we will only need to know how to do it with the assets/ resource directory. Loading a pre-packaged database from File or InputStream is only different depending on the location of the file.

All options require you to export your pre-built database, so we will learn how to do that first.

Export an Existing Database

To be able to export a database, we will need to build it first on a test device. Follow the steps below to create some data and export the database.

  1. Select the top-most app directory of in the Android Project View.

  2. File > New > Folder > Assets Folder.

  3. Start the App in Debug mode.

  4. Open the App Inspection tab to start the Database Inspector.

  5. Run the statement below to add some Person into the database.

     INSERT INTO person VALUES
     (1, "Terence", "Landon"),
     (2, "Danielle", "Alger"),
     (3, "Arnold", "Chandler"),
     (4, "Mariah", "Blake"),
     (5, "Randal", " Maria ")
  6. Now, highlight the my-database and select Export as File. Save it as a .db file.
    5.png

  7. You can export directly into the assets directory created previously.

  8. Optionally, save another copy of the same database as the SQL file extension, and then open the file with a text editor. With the human-readable SQL file, we now understand exactly which statements are run against the underlying SQLite database.

     PRAGMA foreign_keys=OFF;
     BEGIN TRANSACTION;
     CREATE TABLE android_metadata (locale TEXT);
     INSERT INTO android_metadata VALUES('en_US');
     CREATE TABLE `person` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `first_name` TEXT NOT NULL, `last_name` TEXT NOT NULL);
     INSERT INTO person VALUES(1,'Terence','Landon');
     INSERT INTO person VALUES(2,'Danielle','Alger');
     INSERT INTO person VALUES(3,'Arnold','Chandler');
     INSERT INTO person VALUES(4,'Mariah','Blake');
     INSERT INTO person VALUES(5,'Randal',' Maria ');
     CREATE TABLE room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT);
     INSERT INTO room_master_table VALUES(42,'cd4ee16aca420ae15eab14b1baa5cdcf');
     DELETE FROM sqlite_sequence;
     INSERT INTO sqlite_sequence VALUES('person',5);
     COMMIT;
Load prepackaged database from app Asset

Now that we have the exported database, it is quite simple to load it into our database on the first run. All we have to do is to call the function createFromAsset() from the Room.Builder class on our database creation call. The builder step to add is:

.createFromAsset("my-database.db")

so that the previous database builder becomes this:

val db = Room.databaseBuilder(
   applicationContext,
   MyDatabase::class.java,
   "my-database"
)
   .createFromAsset("my-database.db")
   .build()

If you reinstall the app, then you can see that the database now has data upon first boot.

6.png

Solution Code

MainActivity.kt

package com.codelab.daniwebandroidprepopulateroomdatabase

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import androidx.room.Room
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)

       val db = Room.databaseBuilder(
           applicationContext,
           MyDatabase::class.java,
           "my-database"
       )
           .createFromAsset("my-database.db")
           .build()

       lifecycleScope.launch {
           db.personDao().getPersonById(1)
       }
   }
}

MyDatabase.kt

package com.codelab.daniwebandroidprepopulateroomdatabase

import androidx.room.Database
import androidx.room.RoomDatabase

@Database(entities = [Person::class], version = 1)
abstract class MyDatabase : RoomDatabase(){
   abstract fun personDao(): PersonDao
}

Person.kt

package com.codelab.daniwebandroidprepopulateroomdatabase

import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "person")
data class Person(
   @PrimaryKey(autoGenerate = true) val id: Long,
   @ColumnInfo(name = "first_name") val firstName: String,
   @ColumnInfo(name = "last_name") val lastName: String
)

PersonDao

package com.codelab.daniwebandroidprepopulateroomdatabase

import androidx.room.Dao
import androidx.room.Query

@Dao
interface PersonDao {
   @Query("SELECT * FROM person WHERE id=:id")
   suspend fun getPersonById(id: Long): Person?
}

Module build.gradle

plugins {
   id 'com.android.application'
   id 'org.jetbrains.kotlin.android'
   id 'kotlin-kapt'
}

android {
   compileSdk 32

   defaultConfig {
       applicationId "com.codelab.daniwebandroidprepopulateroomdatabase"
       minSdk 21
       targetSdk 32
       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 {
   def room_version = "2.4.2"
   implementation "androidx.room:room-runtime:$room_version"
   kapt "androidx.room:room-compiler:$room_version"
   implementation "androidx.room:room-ktx:$room_version"
   implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'

   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 prepopulate a Room database in this tutorial. The full project code can be found at https://github.com/dmitrilc/DaniwebAndroidPrepopulateRoomDatabase

Imperva Announces New API Security Solution

Imperva, a cyber security company that protects both cloud and on-premises data, has announced a new API security solution that provides API discovery, data classification, and risk mitigation functionality. This release follows recent reports of increasing attacks directed at API traffic. 

What Does AIOps Mean for SREs? It’s Complicated

If you’re an SRE, you might view AIOps with great excitement. By automating complex workflows and troubleshooting processes, AIOps could make your life as an SRE much easier.

Alternatively, SREs may choose to view AIOps with disdain. They might think of AIOps as just a fancy buzzword that doesn’t live up to its promises, and that can become a distraction from the SRE tools that really matter.

Guide to Enterprise AI Platform Selection

This is an article from DZone's 2022 Enterprise AI Trend Report.

For more:


Read the Report

The use of artificial intelligence (AI) in companies is becoming more and more widespread, possibly even trending toward industrialization. Whether this is done on the basis of an existing data science platform or not, using all-in-one tools or not, or hosting data in the cloud or on-premise, there is a large number of software solutions available and, therefore, choices. This profusion of choices should not make us forget the raison d'être of any IT solution! Between those who promise you the moon and those who want the ultimate (and unfeasible) solution, you must stick to your needs more strongly than ever. 

How to Easily Add CSV Import to Your React App

CSV is convenient. The likes of Excel, Google Docs, spreadsheet export functions, and reporting applications all support CSV in some capacity. CSV works well with large data sets and the major perk is that it can easily be converted to other formats such as XML or JSON.

This is where the hard part starts: how do you easily convert CSV data for your app, MVP, or SaaS? For JavaScript-based applications, there are a plethora of free CSV parsers available. While these libraries are great, open-source is known to pose a security risk such as relaxed integration oversight and potentially poor and integrated practices.

How to not mix html/js with php in classes

Hello guys,
I'm using mustache php template engine with classes. My system is extendable via extensions which is injected in basecontroller construct method...
The extensions is working correctly, but in them i have html and js... I want to move html/js in external files but i need to works with the extension. (ajax/post/get etc...)

Can you give me a advice ?
I have event dispatcher too (for the extensions)

https://github.com/igorw/evenement
https://github.com/bobthecow/mustache.php

Here is example of one of my extensions:

<?php
/**
 * ../../ext/pok4/email_subscribe/ext.php
 *
 * @package default
 */


if (count(get_included_files()) == 1) exit("Direct access not permitted."); //Don't edit
//Your Extension Script
class Email_Subscribe extends \App\Controllers\BaseController  {



    /**
     *
     */
    public function __construct() {
        parent::__construct();

        $this->ext_html_position = 'up'; //up or down is valid value for this, you can place it div on top or bottom of your site

        //LANGUAGE SETTER
        if (get_current_language() == 'bg') {
            $this->lang= array_merge($this->lang, [
                    'ext_email_subscribe_welcome'=>'!            -',
                    'ext_email_subscribe_button'=>' ',
                    'ext_email_subscribe_enter'=>' ',
                    'ext_email_subscribe_denied'=>' ',
                ]);
        }
        if (get_current_language() == 'en') {
            $this->lang= array_merge($this->lang, [
                    'ext_email_subscribe_welcome'=>'Hello! You can subscribe for our news, just type your email below',
                    'ext_email_subscribe_button'=>'I subscribe',
                    'ext_email_subscribe_enter'=>'Write your email',
                    'ext_email_subscribe_denied'=>'Refuse',
                ]);
        }
        if (get_current_language() == 'ru') {
            $this->lang= array_merge($this->lang, [
                    'ext_email_subscribe_welcome'=>'!      ,      ',
                    'ext_email_subscribe_button'=>'',
                    'ext_email_subscribe_enter'=>'   ',
                    'ext_email_subscribe_denied'=>' ',
                ]);
        }
        if (get_current_language() == 'es') {
            $this->lang= array_merge($this->lang, [
                    'ext_email_subscribe_welcome'=>'Hola! Puede suscribirse a nuestras noticias, simplemente escriba su correo electrnico a continuacin',
                    'ext_email_subscribe_button'=>'Yo suscribo',
                    'ext_email_subscribe_enter'=>'Escribe tu correo electrnico',
                    'ext_email_subscribe_denied'=>'Rehusar',
                ]);
        }
        //debug
        //var_dump($this->lang['ext_email_subscribe_welcome']);
        //var_dump($this->lang['ext_email_subscribe_button']);



    }



    /**
     *
     */
    public function load() {


        if ($_SERVER['REQUEST_URI'] == '/' && (!isset($_COOKIE['argos_mail_subscribe_denied']) || !isset($_COOKIE['argos_email_subscribe_ok']))) {
            $this->dispatcher->emit('core_event_head_append', ['<link rel="stylesheet" href="ext/pok4/email_subscribe/css/style.css">']);

            if(get_style() == 'orizon' || get_style()== 'revelio') {
                $this->dispatcher->emit('core_event_head_append', ['<link rel="stylesheet" href="ext/pok4/email_subscribe/css/past.css">']);
            }

            $format_html_for_js = preg_replace('~>\s+<~', '><', addslashes(
                    '<section class="home-newsletter">
                    <div class="container" style="margin-top:0 !important">
                    <div class="row">
                    <div class="col-sm-12">
                        <div class="single">
                        <h2>'.$this->lang['ext_email_subscribe_welcome'].'</h2>
                        <form method="post">
                        <div class="input-group">
                             <input type="email"name="subscriber_email" class="form-control get_email_for_sub" placeholder="'.$this->lang['ext_email_subscribe_enter'].'">
                             <span class="input-group-btn">
                             <button class="btn btn-theme" name="send_email_sub" type="submit">'.$this->lang['ext_email_subscribe_button'].'</button>
                             </span>
                        </div>
                        </form>
                        </div>
                    </div>
                    </div>
                    </div>
                    <div class="denied_email_sub center-block text-center"><a href="#" style="text-decoration: none;"><button type="button" class="btn-close float-button-close" aria-label="Close"></button>&nbsp;<span style="color:red">'.$this->lang['ext_email_subscribe_denied'].'</span></a></div>
                    </section>'
                ));


            if ($this->ext_html_position =='up') {
                $send_div = 'prependTo("body");';
            } else {
                $send_div = 'appendTo("body");';
            }

            $this->dispatcher->emit('core_event_inside_head_ready_front', ['

             var cookie_check = getCookie("argos_email_subscribe_ok");
             var cookie_check2 = getCookie("argos_mail_subscribe_denied");
             if(cookie_check == null  && cookie_check2 == null) {
                 $("'.$format_html_for_js.'").'.$send_div.'

             } else {
                $(".home-newsletter").remove();
             }


             //Detect on click
             $( ".denied_email_sub" ).click(function() {
                  document.cookie=\'argos_mail_subscribe_denied=1;expires=Wed, 31 Oct 3099 08:50:17 GMT;path=/\';
                  $(".home-newsletter").remove();
             });

            ']);

            //submit form
            if (isset($_POST['send_email_sub'])) {
                $user_ip = $this->user_ip;
                $email = $_POST['subscriber_email'];

                //check if email exists
                $checker =  $this->db->prepare("SELECT * FROM ".$this->argos_db_prefix."emails WHERE email=?");
                $checker->bindParam(1, $email, PDO::PARAM_STR);
                $checker->execute();
                if ($checker->rowCount() < 1) {
                    //send data to db
                    $go = $this->db->prepare("INSERT INTO ".$this->argos_db_prefix."emails (`email`,`user_ip`) VALUES(?,?)");
                    $go->bindParam(1, $email, PDO::PARAM_STR);
                    $go->bindParam(2, $user_ip, PDO::PARAM_STR);
                    $go->execute();
                }
                //send cookie to user to prevent box showing
                setcookie('argos_email_subscribe_ok', '1', time()+60*60*24*365, '/');
            }


        }
    }


};




$load_ext = new Email_Subscribe;
$load_ext->load();

Getting Started With Kubernetes Policy Management, Kyverno on OpenShift Container Platform

Red Hat® OpenShift is a widely adopted Container Platform powered by Kubernetes. As the enterprise adoption of OpenShift grows, operators are often faced with the need to automatically update or generate configuration as well as ensure security and enforce best practices. Essentially they are looking to provide guardrails so that developers can continue to use OpenShift without impacting other applications or introducing security vulnerabilities via misconfigurations. Kyverno, a Kubernetes-native policy engine, is perfect for this task and is often being used to address the above-mentioned challenges. In this post, I will discuss how you can get started with Kyverno on the OpenShift Container Platform.

Red Hat OpenShift

Red Hat® OpenShift® Container Platform is the industry-leading hybrid cloud platform powered by containers and Kubernetes. Using the OpenShift Container Platform simplifies and accelerates the development, delivery, and lifecycle management of a hybrid mix of applications, consistently anywhere across on-premises, public clouds, and Edge. OpenShift Container Platform is designed to deliver continuous innovation and speed at any scale, helping organizations to be ready for today and build for the future.

How to Configure Selenium in Eclipse

Managing user experience is pivotal for software development. Test automation enables user preferences and convenience to remain at the center of the development process while saving time and effort. That is why comprehensive automation testing has become necessary to retain customers and meet their expectations. With significantly shorter time frames for development, Selenium Testing, in particular, has become an integral part of the development to facilitate automated testing of web applications. 

Selenium is the most popular automated tool in existence today. 59.5% of people consider Selenium for Cross Browser Testing because of the robustness and flexibility it offers by supporting multiple languages like Java, C#, Python, Perl, Ruby, etc. However, a majority (67%) of the Selenium users prefer Java as their language for Selenium Testing.

My Amazon SDE 2 Interview Experience

I recently appeared for an Amazon SDE 2 interview, and this article will document my experience. The entire process took about three months, from the day the recruiter contacted me to the time I got the offer.

Amazon SDE-2 interview process has one telephonic screening and four on-site rounds. Making a total of five rounds. Sometimes, there are additional rounds conducted as a part of on-site interview. [Because of the global pandemic everything was conducted virtually.]

How To Build a Self-Serve Data Architecture for Presto Across Clouds

This article highlights the synergy between the two widely adopted open-source projects, Alluxio and Presto, and demonstrates how together they deliver a self-serve data architecture across clouds

What Makes an Architecture Self-Serve?

Condition 1: Evolution of the Data Platform Does Not Require Changes

All data platforms evolve over time, including the addition of a new data store, compute engine, or a new team that needs to access shared data. In either case, a data platform is self-serve if it does not require changes to accommodate evolution.

What Is Breadth-First Search?

Why Should I Care?

A lot of algorithms are implemented for you as part of your chosen language. That means that they are interesting to learn about, but you'll rarely write them yourself.

Graph traversal algorithms are different. We use graphs all the time, from linking related products in an e-commerce application to mapping relationships between people in a social network.