I have the below code, however the Preview is rendering the view different to when I run it on a AVD (Android Virtual Device). It looks there are 2 things different:
Firstly, the AVD is rending the view at the top of the screen, but the preview is rending the view in the centre of the screen. I've added contentAlignment = Alignment.Center to my AlertView Box which I believe should render it in the middle of the screen.
Secondly, the AVD is showing a weird shadow compared to the preview.
Images below of the preview and AVD:
//Preview Provider
#Preview(
showSystemUi = true,
device = Devices.PIXEL_4_XL,
uiMode = Configuration.UI_MODE_NIGHT_NO,
showBackground = true,
backgroundColor = 0xFFFFFFFF
)
#Preview(
showSystemUi = true,
device = Devices.PIXEL_4,
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
backgroundColor = 0xFF000000
)
#Composable
fun DefaultPreview() {
AlertView()
}
//AlertViewModel
class AlertViewModel : ViewModel() {
var showAlert: Boolean by mutableStateOf(false)
var alertIcon: ImageVector by mutableStateOf(Icons.Rounded.WifiOff)
var alertTitle: String by mutableStateOf("No internet connection")
var alertMessage: String by mutableStateOf("This is the alert message and it is purposely longer than one line to be displayed on screen")
var alertButtonText: String by mutableStateOf("This is the alert button text")
}
//--------------------------------------------------------------------------------------------------
//.onCreate
class AlertManager : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AlertView()
}
}
}
//AlertView
#Composable
fun AlertView(alertViewModel: AlertViewModel = viewModel()) {
Box(
contentAlignment = Alignment.Center
) {
Column(
verticalArrangement = Arrangement.spacedBy(10.dp),
modifier = Modifier
.padding(30.dp, 0.dp)
.background(
shape = RoundedCornerShape(15.dp),
color =
if (isSystemInDarkTheme()) {
Color.Black
} else {
Color.White
}
)
.shadow(
elevation = 1.dp,
shape = RoundedCornerShape(15.dp),
ambientColor = if (isSystemInDarkTheme()) {
Color.Black
} else {
Color.White
},
spotColor = if (isSystemInDarkTheme()) {
Color.White
} else {
Color.Gray
},
)
.padding(30.dp, 25.dp),
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(20.dp)
) {
Icon(
imageVector = alertViewModel.alertIcon,
contentDescription = null,
tint = if (isSystemInDarkTheme()) {
Color.White
} else {
Color.Black
},
)
Text(
text = alertViewModel.alertTitle,
style = TextStyle(
fontSize = 20.sp,
fontWeight = FontWeight.SemiBold,
color = if (isSystemInDarkTheme()) {
Color.White
} else {
Color.Black
}
)
)
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(25.dp),
) {
Text(
text = alertViewModel.alertMessage,
color = if (isSystemInDarkTheme()) {
Color.White
} else {
Color.Black
}
)
Button(
onClick = {
alertViewModel.showAlert = false
},
colors = ButtonDefaults.buttonColors(
backgroundColor = if (isSystemInDarkTheme()) {
Color.White
} else {
Color.Black
}
),
shape = RoundedCornerShape(15.dp),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 10.dp),
contentPadding = PaddingValues(15.dp),
) {
Text(
text = alertViewModel.alertButtonText,
fontWeight = FontWeight.Bold,
color = if (isSystemInDarkTheme()) {
Color.Black
} else {
Color.White
}
)
}
}
}
}
}
My app has a navigation bar and in one navBarItem I have two tabs, I want to read images and their caption I have stored in my firebase firestore collection into a LazyColumn in one of the tabs.
Here is how my DataException and GalleryRepo class looks like
data class DataOrException<T, E : Exception> (
var data: T? = null,
var e: E? = null
)
#Singleton
class GalleryRepo #Inject constructor(
private val queryImageData: Query
) {
fun getImagesFromFirestore(): DataOrException<List<PQImageGallery>, Exception> {
val dataOrException = DataOrException<List<PQImageGallery>, Exception>()
try {
dataOrException.data = queryImageData.get()
.result.map { document -> document.toObject(PQImageGallery::class.java) }
} catch (e: FirebaseFirestoreException) {
dataOrException.e = e
}
return dataOrException
}
}
My data class
data class PQImageGallery (
var imageUrl: String? =null,
var caption: String? = null
)
ViewModels...
#HiltViewModel
class PQViewModel #Inject constructor(
private val repository: GalleryRepo
): ViewModel() {
var loading = mutableStateOf(false)
val data: MutableState<DataOrException<List<PQImageGallery>, Exception>> = mutableStateOf(
DataOrException(
listOf(),
Exception("")
)
)
init {
getGalleryCollection()
}
private fun getGalleryCollection() {
viewModelScope.launch {
loading.value = true
data.value = repository.getImagesFromFirestore()
loading.value = false
}
}
}
#Module
#InstallIn(SingletonComponent::class)
object RemoteImageModel {
#Provides
#Singleton
fun loadImagesInCollection() = FirebaseFirestore.getInstance()
.collection(IMAGE_COLLECTION)
}
My GalleryCollection composable file
#Composable
fun GalleryCollection(dataOrException: DataOrException<List<PQImageGallery>, Exception>) {
val images = dataOrException.data
images?.let {
LazyColumn {
items(items = images) { product ->
GalleryCard(pqImageGallery = product)
}
}
}
val e = dataOrException.e
e?.let {
Text(
text = e.message!!,
modifier = Modifier.padding(16.dp)
)
}
}
#Composable
fun GalleryCard(
pqImageGallery: PQImageGallery,
) {
Card(...) {
Box(modifier = Modifier
.fillMaxSize()
) {
pqImageGallery.imageUrl?.let { imageUrl ->
CoilImage(
data = imageUrl,
....
)
}
pqImageGallery.caption?.let { caption ->
Text(
text = caption,
...
)
}
}
}
}
My navBarItem that contains the two tabs called Home
class Home : ComponentActivity() {
private val viewModel: PQViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
HomeScreen(viewModel)
}
}
}
#Composable
fun HomeScreen(
viewModel: PQViewModel
) {
val dataOrException = viewModel.data.value
val navController = rememberNavController()
val currentBackStack by navController.currentBackStackEntryAsState()
val currentDestination = currentBackStack?.destination
val currentScreen =
HomeTabRowScreens.find { it.route == currentDestination?.route } ?: GalleryView
Scaffold(
topBar = {
HomeScreenTabRow(
allScreens = HomeTabRowScreens,
onTabSelected = { newScreen ->
navController
.navigateSingleTopTo(newScreen.route)
},
currentScreen = currentScreen,
)
}
) { innerPadding ->
NavHost(
navController = navController,
startDestination = GalleryView.route,
modifier = Modifier.padding(innerPadding)
) {
composable(route = GalleryView.route) {
// GalleryTabScreen()
GalleryCollection(dataOrException = dataOrException)
}
composable(route = VirtualTourView.route) {
VirtualTourScreen()
}
}
}
}
fun NavHostController.navigateSingleTopTo(route: String) =
this.navigate(route) {
popUpTo(
this#navigateSingleTopTo.graph.findStartDestination().id
) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
Finally calling the Home class in my main screen(MainActivity)
NavHost(
..
) {
composable(NAV_HOME) {
Home()
}
composable(..) { ...}
}
The issue is when I run the app the tabs in the Home navBar doesn't show at all talk of loading the images from the firestore collection.
Also, is there another way to instantiate a viewModel without having to create a class that extends ComponentActiviy?
If there's another way to achieve this kindly share.
I am writing a small application in Kotlin with JetpackCompose and my application freezes (CountDownLatch) when a request is called, is there any way to avoid this?
Without CountDownLatch, okhttp does not have time to return the result before the method completes.
MainActivity.kt
fun infoServer() {
val viewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
var txt = remember {
mutableStateOf(0)
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Row(
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.padding(10.dp)
) {
Text(
text = "Player Count ",
style = MaterialTheme.typography.body1
)
Text(
text = txt.value.toString(),
style = MaterialTheme.typography.body1,
fontWeight = FontWeight.Bold
)
}
Row(horizontalArrangement = Arrangement.Center) {
eveButton(
text = "Update",
onClick = ({
viewModel.updatePlayerCount()
txt.value = viewModel.res
}),
modifier = Modifier
.padding(10.dp)
)
eveButton(
text = "Clear",
onClick = ({
txt.value = 0
}),
modifier = Modifier
.padding(10.dp)
)
}
}
}
MainActivityViewModel.kt
class MainActivityViewModel : ViewModel() {
var res = 0
fun updatePlayerCount() {
res = MainActivityModel().updateServerPlayers()
}
}
MainACtivityModel.kt
class MainActivityModel {
var result = 0
fun updateServerPlayers(): Int {
val client = OkHttpClient().newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.callTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build()
val request = Request.Builder()
.url("https://esi.evetech.net/latest/status/?datasource=tranquility")
.method("GET", null)
.build()
var countDownLatch = CountDownLatch(1)
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.d("Error Connection", e.toString())
}
override fun onResponse(call: Call, response: Response) {
response.use {
if (!response.isSuccessful) throw IOException("Error $response")
try {
var json =JSONObject(response.body!!.string())
countDownLatch.countDown()
result = json.getInt("players")
}
catch (e: Exception) {
Log.d("OKHTTP", e.toString())
}
}
}
})
countDownLatch.await()
Log.d("OKHTTP2", result.toString())
return result
}
}
How can I make a Form in which the elements are automatically divided into sections based on their first letter and add to the right the alphabet jumper to show the elements starting by the selected letter (just like the Contacts app)?
I also noted a strange thing that I have no idea how to recreate: not all letters are shown, some of them appear as "•". However, when you tap on them, they take you to the corresponding letter anyway. I tried using a ScrollView(.vertical) inside a ZStack and adding .scrollTo(selection) into the action of the Buttons, however 1) It didn't scroll to the selection I wanted 2) When I tapped on the "•", it was as if I was tapping on all of them because they all did the tapping animation 3) I wasn't able to divide the List as I wanted to.
I have this:
import SwiftUI
struct ContentView: View {
let alphabet = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W", "X","Y", "Z"]
let values = ["Avalue", "Bvalue", "Cvalue", "Dvalue"]
var body: some View {
ScrollViewReader{ scrollviewr in
ZStack {
ScrollView(.vertical) {
VStack {
ForEach(alphabet, id: \.self) { letters in
Button(letters){
withAnimation {
scrollviewr.scrollTo(letters)
}
}
}
}
}.offset(x: 180, y: 120)
VStack {
ForEach(values, id: \.self){ vals in
Text(vals).id(vals)
}
}
}
}
}
}
But I'd want it like this:
import SwiftUI
struct AlphabetSort2: View {
let alphabet = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W", "X","Y", "Z"]
let values = ["Avalue", "Bvalue", "Cvalue", "Dvalue", "Mvalue", "Zvalue"]
var body: some View {
ScrollView {
ScrollViewReader { value in
ZStack{
List{
ForEach(alphabet, id: \.self) { letter in
Section(header: Text(letter)) {
ForEach(values.filter { $0.hasPrefix(letter) }, id: \.self) { vals in
Text(vals).id(vals)
}
}.id(letter)
}
}
HStack{
Spacer()
VStack {
ForEach(0..<alphabet.count, id: \.self) { idx in
Button(action: {
withAnimation {
value.scrollTo(alphabet[idx])
}
}, label: {
Text(idx % 2 == 0 ? alphabet[idx] : "\u{2022}")
})
}
}
}
}
}
}
}
}
struct AlphabetSort2_Previews: PreviewProvider {
static var previews: some View {
AlphabetSort2()
}
}
Add an swipe-up-and-down action for the alphabet jumper, so we can get an UILocalizedIndexCollation -like swipe effect, it works for view and add mode, but delete, I guess it is due to SwiftUI's UI refresh mechanism.
extension String {
static var alphabeta: [String] {
var chars = [String]()
for char in "abcdefghijklmnopqrstuvwxyz#".uppercased() {
chars.append(String(char))
}
return chars
}
}
struct Document: View {
#State var items = ["Alpha", "Ash", "Aman", "Alisia", "Beta", "Baum", "Bob", "Bike", "Beeber", "Beff", "Calipha", "Cask", "Calf", "Deamon", "Deaf", "Dog", "Silk", "Seal", "Tiger", "Tom", "Tan", "Tint", "Urshinabi", "Verizon", "Viber", "Vein", "Wallet", "Warren", "Webber", "Waiter", "Xeon", "Young", "Yoda", "Yoga", "Yoger", "Yellow", "Zeta"]
var body: some View {
ScrollViewReader { scrollView in
HStack {
List {
ForEach(String.alphabeta, id: \.self){ alpha in
let subItems = items.filter({$0.starts(with: alpha)})
if !subItems.isEmpty {
Section(header: Text(alpha)) {
ForEach(subItems, id: \.self) { item in
Text(item)
}.onDelete(perform: { offsets in
items.remove(at: offsets.first!)
})
}.id(alpha)
}
}
}
VStack{
VStack {
SectionIndexTitles(proxy: scrollView, titles: retrieveSectionTitles()).font(.footnote)
}
.padding(.trailing, 10)
}
}
}
// .navigationBarTitleDisplayMode(.inline)
// .navigationBarHidden(true)
}
func retrieveSectionTitles() ->[String] {
var titles = [String]()
titles.append("#")
for item in self.items {
if !item.starts(with: titles.last!){
titles.append(String(item.first!))
}
}
titles.remove(at: 0)
if titles.count>1 && titles.first! == "#" {
titles.append("#")
titles.removeFirst(1)
}
return titles
}
}
struct Document_Previews: PreviewProvider {
static var previews: some View {
Document()
}
}
struct SectionIndexTitles: View {
class IndexTitleState: ObservableObject {
var currentTitleIndex = 0
var titleSize: CGSize = .zero
}
let proxy: ScrollViewProxy
let titles: [String]
#GestureState private var dragLocation: CGPoint = .zero
#StateObject var indexState = IndexTitleState()
var body: some View {
VStack {
ForEach(titles, id: \.self) { title in
Text(title)
.foregroundColor(.blue)
.modifier(SizeModifier())
.onPreferenceChange(SizePreferenceKey.self) {
self.indexState.titleSize = $0
}
.onTapGesture {
proxy.scrollTo(title, anchor: .top)
}
}
}
.gesture(
DragGesture(minimumDistance: indexState.titleSize.height, coordinateSpace: .named(titles.first))
.updating($dragLocation) { value, state, _ in
state = value.location
scrollTo(location: state)
}
)
}
private func scrollTo(location: CGPoint){
if self.indexState.titleSize.height > 0{
let index = Int(location.y / self.indexState.titleSize.height)
if index >= 0 && index < titles.count {
if indexState.currentTitleIndex != index {
indexState.currentTitleIndex = index
print(titles[index])
DispatchQueue.main.async {
let impactMed = UIImpactFeedbackGenerator(style: .medium)
impactMed.impactOccurred()
// withAnimation {
proxy.scrollTo(titles[indexState.currentTitleIndex], anchor: .top)
// }
}
}
}
}
}
}
struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
value = nextValue()
}
}
struct SizeModifier: ViewModifier {
private var sizeView: some View {
GeometryReader { geometry in
Color.clear.preference(key: SizePreferenceKey.self, value: geometry.size)
}
}
func body(content: Content) -> some View {
content.background(sizeView)
}
}
I created this class, And I want to load thumbnails into the iconview as a different thread for efficiency reasons, because the gui load very slow if I do it in the same thread. But when I create the thread, it doesn't works, it draw some thumbnails and then they dissapear. When I use join, it works. This is my code:
public class FotoThumbnailPane : Gtk.ScrolledWindow{
private FotoThumbnailPane_i pane;
private string namet;
public FotoThumbnailPane(string name){
this.namet = name;
}
public void set_imagelist(fileutils.ImageList image_list){
pane = new FotoThumbnailPane_i(image_list);
this.add (pane);
this.set_min_content_width(140);
this.show_all();
}
//This is my threaded function
public void* load_thumbs(){
pane.set_visible(false);
pane.newmodel = new Gtk.ListStore (2, typeof (Gdk.Pixbuf), typeof (string));
pane.set_selection_mode (Gtk.SelectionMode.SINGLE);
pane.set_pixbuf_column (0);
pane.set_model(pane.newmodel);
string icon_style = """
.thumbnail-view {
background-color: #FFFFFF;
}
.thumbnail-view:selected {
background-color: #9D9D9D;
border-color: shade (mix (rgb (34, 255, 120), #fff, 0.5), 0.9);
}
""";
var icon_view_style = new Gtk.CssProvider ();
try {
icon_view_style.load_from_data (icon_style, -1);
} catch (Error e) {
warning (e.message);
}
pane.get_style_context ().add_class ("thumbnail-view");
pane.get_style_context ().add_provider (icon_view_style, Gtk.STYLE_PROVIDER_PRIORITY_THEME);
//Add thumbnails to the iconview
string buff;
for(int i=1; i<pane.image_list.size; i++){
buff = pane.image_list.get_full_filename(i);
stdout.printf("Added %s to thumbnail\n", buff);
var image = new Gdk.Pixbuf.from_file_at_scale(buff, 110, 80, false);
// Add the wallpaper name and thumbnail to the IconView
Gtk.TreeIter root;
pane.newmodel.append(out root);
pane.newmodel.set(root, 0, image, -1);
pane.newmodel.set(root, 1, pane.image_list.get_filename(i), -1);
// Select the thumbnail if it is the first in list
if (i==0) {
pane.select_path (pane.newmodel.get_path (root));
}
pane.iters.append (root);
}
pane.set_sensitive(true);
this.queue_draw();
return null;
}
}
You don't actually need to deal with threads in your program--you can just use an asynchronous method to load the content. Specifically, Gdk.Pixbuf.new_from_stream_at_scale_async.
Here is an example:
public async void load (Gtk.Image img, string filename) {
GLib.File file = GLib.File.new_for_commandline_arg (filename);
try {
GLib.InputStream stream = yield file.read_async ();
Gdk.Pixbuf pixbuf = yield Gdk.Pixbuf.new_from_stream_at_scale_async (stream, 320, -1, true);
img.set_from_pixbuf (pixbuf);
} catch ( GLib.Error e ) {
GLib.error (e.message);
}
}
private static int main (string[] args) {
GLib.return_val_if_fail (args.length > 1, -1);
Gtk.init (ref args);
Gtk.Window win = new Gtk.Window ();
win.destroy.connect (() => {
Gtk.main_quit ();
});
Gtk.Image image = new Gtk.Image ();
win.add (image);
load.begin (image, args[1], (obj, async_res) => {
GLib.debug ("Finished loading.");
});
win.show_all ();
Gtk.main ();
return 0;
}