• úvod
  • témata
  • události
  • tržiště
  • diskuze
  • nástěnka
  • přihlásit
    registrace
    ztracené heslo?
    LWEEKAndroid development
    KAERO
    KAERO --- ---
    Chtel jsem poustet nejake programy, co jsou jen na androidu. Ale nechci si je strkat na telefon ("aplikace" Lego Boost, ovladani cinskeho robota). Tak jsem se kouknul na moznosti virtualizace androidu v linuxu.

    Jel jsem podle tohoto navodu:
    Best Way to Run Android Apps and Games on Linux – Linux Hint
    https://linuxhint.com/android_apps_games_linux/
    Anbox: funguje, ale jen pro nektere programy. Treba ctecka knih v poho, ale ty co potrebuju ne - zobrazi se jen prazdne okno. Problem s graficky narocnejsimi programy?
    Arc Welder: opet, nektere programy jedou, ty co potrebuju ne.
    Genymotion: cloud nechci, mam rad veci doma na svem disku.
    Android-x86: reseni pres virtual box - to vypadalo dobre. Bohuzel ale casto programy uvnitr padaji, treba google play jsem spustil az na potreti, a pri aktualizaci zase chyba. Pisou ze je potreba dat vice procesoru, ale ani 4 nepomohly. Nedohledal jsem kde je problem.
    Android Studio IDE: pripada mi ze tohle neni pouzitelne na pravidelne spousteni nekolika androidich programu. Jde ta virtualizace postet bez celeho vyvojoveho prostredi? Jeste jsem to uplne nerozchodil.

    Tak nejak jsem mel pocit, ze to s androidem bude jednoduche. Jeden balik z repozitaru a budu mit kompletni virtualni android. Ale ono ne.

    Virtualizujete nejak android? Minul jsem nejakou moznost?
    CNV
    CNV --- ---
    ADAMH: Takže postarat se o to ručně. Dík, to jsem chtěla vědět.
    ADAMH
    ADAMH --- ---
    CNV: Pri kazde zmene zvysit cislo verze SQLiteOpenHelper a v onUpgrade vyresit adekvatni zmenou, tj alter table pro upravy, create table pro nove tabulky.

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    if (oldVersion <2) {

    db.execSQL("create table blabla("
    + "_id integer primary key,"
    + "blabla integer,"
    + "blabla2 int"
    + ");");

    }

    if (oldVersion <3) {

    db.execSQL("ALTER TABLE blabla ADD COLUMN blabla2 integer");
    }
    CNV
    CNV --- ---
    Mám začátečnický dotaz. Mám v aplikaci SQLite databázi, ve které jsou tabulky s různými položkami a jedna tabulka, jejíž obsah mění uživatel (v aplikaci si zaklikává, které z těch položek má a kolik kusů). Když do databáze přidám další tabulky a rozešlu uživatelům nové APK, přepíše se jim komplet databáze, nebo se jen přidají nové tabulky?

    Snažím se pochopit, jak updaty fungujou, ale literatura a tutorialy se těmahle "drobnostma" moc nezabývají :)
    ADAMH
    ADAMH --- ---
    JOHNY_G: Dik,
    1) zkusim, koukam ze mam nejakou 3.6 a uz je venku 4.0
    2) zkusim zda to bude na to atributy v xml reagovat v kdyz ne tak pres tu javu, nejak si prepisu.
    JOHNY_G
    JOHNY_G --- ---
    ADAMH:
    1) Tohle vypadá na bug, jestli jsi do toho opravdu nijak nezasáhl. Zkus jinou verzi Android Studia, Build Tools, SDK... Nebo si to prostě udělej ručně podle nějakého tutorialu.

    2) Pokud používáš výhradně navigation components, tak se backstack řídí atributem popUpTo. Prakticky ti pokryje všechny běžné scénáře v momentě, kdy pochopíš root node (u tebe to bude asi @id/mobile_navigation, prostě ID elementu navigation ve tvém navigačním grafu) a popUpToInclusive. Je to ovšem poměrně neintuitivní, takže se obrň trpělivostí, a navíc to neřeší některé edge casy, typicky na starších Androidech. Takže se můžeš dostat do situace, kdy musíš přetížit onBackPressed a/nebo onSupportNavigateUp v aktivitě (není to totéž, i když se to tak bohužel v praxi často používá).

    Dám ti na to i příklady, ale jen v Kotlinu, protože jsem líný zrovna tohle přepisovat do Javy, kde by to byl nesrovnatelně delší kód.

    Mám dva jednoduché interfacy, které můžou a nemusí Fragmenty implementovat:
    interface IOnBackPressed {
        fun onBackPressed(): Boolean
    }
    
    interface IOnUpPressed {
    	fun onUpPressed(): Boolean
    }

    Aktivita se vždycky zeptá Fragmentu, jestli si to ohandluje sám:
    override fun onBackPressed() {
    	val fragment = nav_host_fragment?.childFragmentManager?.primaryNavigationFragment
    	// Teď se zeptám jestli mám vůbec referenci na aktivní fragment, jestli implementuje IOnBackPressed, a pokud ano, tak jestli ho ohandloval/zkonzumoval
    	if ((fragment as? IOnBackPressed)?.onBackPressed() != true) { 
    		super.onBackPressed()
    	}
    }
    
    override fun onSupportNavigateUp(): Boolean {
    	val fragment = nav_host_fragment?.childFragmentManager?.primaryNavigationFragment
    	return if ((fragment as? IOnUpPressed)?.onUpPressed() != true) {
    		// Tady se ještě zeptám navControlleru, jestli se měl kam vrátit; pokud ne, tak byl uživatel v rootu a ve skutečnosti kliknul v toolbaru nikoli na šipku, ale na ikonu menu, takže potřebuju otevřít hamburger menu.
    		// Ne vždycky se to musí řešit takhle, ale já mám v tomhle projektu z businessových požadavků dost komplikovanou definici rootu :-D
    		if (navController.navigateUp(appBarConfig)) {
    			true
    		} else {
    			drawer_layout.openDrawer(GravityCompat.START)
    			false
    		}
    	} else {
    		true
    	}	
    }

    A jednotlivé fragmenty mi oznámí, jestli mají nějakou tu výjimku. Pokud ji nemají vůbec, tak ty interfacy vůbec neimplementuju, ale můžou prostě vracet false:
    // Tenhle fragment se vždycky šipkou v toolbaru vrací na homescreen, ať se tam šlo odkudkoli, neboť si to klient přál a na legacy Androidech zlobí popUpTo do rootu
    override fun onUpPressed(): Boolean {
    	navController.navigate(ResultFragmentDirections.goToHome())
    	return true
    }
    
    // Tenhle fragment používá na přáni klienta custom klávesnici, takže emuluji chování té systémové, která se tlačítkem zpět nejprve schová (a hodnotou true zabrání aktivitě návrat ve stacku) a až když je schovaná, tak dalším stiskem vyhodí false a nechá aktivitu dělat její práci.
    // To je třeba hezký příklad toho, že back (tlačítko/gesto) není to samé jako up (šipka v toolbaru), protože tam se vracíme rovnou, klávesnice neklávesnice:
    override fun onBackPressed(): Boolean {
    	if (viewModel.keyboardShown.get()) {
    		viewModel.keyboardShown.set(false)
    		return true
    	}
    
    	return false
    }

    Jak vidíš z těch příkladů, jsou to vážně edge cases, které v zásadě odporují konvencím platformy. Když ti nedýchá na záda klient, měl by sis vystačit s navigačním grafem.
    ADAMH
    ADAMH --- ---
    DRIZDIK: 1) No těžko říct zda ano a jak to zjistit, čekal bych od templatu vytvoření aplikace že bude funkční ve stavu v jakém je vytvořen.
    2) jediné co jsem našel jako asi správné je action element v "mobile_navigation.xml" ale nastavit tam back button nevidím, návody na to nejsou, je to asi relativně nové
    DRIZDIK
    DRIZDIK --- ---
    ADAMH:
    1) očekávám že přes to máš ještě nějaký layout, který ti zachytává dotyky
    2) dvě možnosti .. bud používáš jen fragmenty a zpět je backstack pop (všechno ostatní si musíš dělat manuálně), případně používáš navigation component, kde si v tom grafu určuješ, jak se má navigace v jednotlivých přechodech chovat
    ADAMH
    ADAMH --- ---
    Zeptal bych se znova, pokud bude mít ještě někdo chuť odpovedět.

    1) v čisté nové aplikaci bez jakéhokoli zásahu mě v emulátoru ani v mobilu nereaguje aplikace na touch v menu (navigation drawer) s navcontrolerem, divny, ze pokud si v emulatoru otevru to menu mysi a pak klikam na šipky na klávesnici nahoru a dolu a dám enter tak se ten fragment změní, ale klasickej dotyk nic, co tam schází?

    2) chtěl bych to dělat správně, ale např jsem narazil, že nevím jak určit jaký fragment se má aktivovat po zmačknutí tlačítka zpět, předpokládal bych že to jde přes XML "mobile_navigation.xml" , overidovat onBackPressed v activitě se mě moc nechce , když jedu na fragmenty

    To by bylo asi prozatím vše.
    ADAMH
    ADAMH --- ---
    DRIZDIK: No když se človek podívá na cízí aplikace, tak tam často také ty fragmenty nenajde. Nejsem ani tak junior, jen spíše samouk. Mám appku s 1M staženími a dělal jsem i s libgdx což také nezkouší každý. Když se človek podívá na nejstahovanější či hodně stahované aplikace vidí tam ještě dnes zvěrstva typu žádosti o přístupu k fotaku či uložisti a to jen proto, že developer neumi používat fileprovider (když chce sdilet screenshot) či intenty.

    Rád se přiučím, nemám problém se zeptat, problém je v tom , že jen málo lidi co poradí to dělá správně. A to je vlastně to co píšeš v poslední větě :)
    DRIZDIK
    DRIZDIK --- ---
    ADAMH: Ale tvoje obtíže by ti měli dát nahlédnout do toho jak dělat správnou architekturu. Activita nebo Fragment je jen View a nemělo by zastupovat funkci modelu. Plus aktivita a fragment ti mohou kdykoliv umřít, když uživatel otočí displej nebo dá appku na pozadí. Ale nic si z toho nedělej, postupně budeš potkávat zdrojáky, kde zjistít, že ani lidé, kteří se prezentují jako velmi seniorní, to nevědí :-)
    ADAMH
    ADAMH --- ---
    JOHNY_G: No jasny, staticka je ale oproti těm bežným statickým objektům má vnitřní hodnoty. Zkousel jsem teď hledat další příklady in-memory singletonu a zdá se, že destroy nikdo neřeší. Tj nenašel jsem tam žádnou zmínku že by ho někdo definoval a ani příklad co by to mělo obsahovat. Takto to bude asi funkční a stabilní.
    JOHNY_G
    JOHNY_G --- ---
    No ta instance právě statická je. Proto bys tam měl přidat ještě alespoň ten destroy, který tu referenci vynuluje, až ji nebudeš potřebovat :-). Lepší je mít nějaký robustnější dependency management, který ti ten životní cyklus uřídí v rámci definovaného scopu a s efektivnější thread safety, ale připadalo mi, že to není tvůj případ :-). Tohle by měl snad přinejhorším požrat garbage collector, až Android tu aplikaci zcela ukončí.
    ADAMH
    ADAMH --- ---
    JOHNY_G: Ja ty sharedpreference pouzivam zpravidla na data co se maji ulozit z nastaveni a pri dalsim spusteni appky nacist. Necekal bych ze je pouziju jen na proste predavani dat v ramci jedne aplikace a jednoho spusteni, podobne i sqlite se me zda takova naddimenzovana.

    JOHNY_G: Tohle zacina byt zajimavejsi. To ze funguje bez predani instance (tj to co jsem doted resil ze jsem musel do kazdeho fragmentu instanci objektu nejak predat) a vypada jako staticky a pritom neni je elegantni, vyzkousim, diky.
    JOHNY_G
    JOHNY_G --- ---
    Ten in-memory singleton může být asi takhle triviální. Má to své mouchy (slušelo by se doimplementovat alespoň destroy(), když už nic jiného), ale už v této podobě to bude daleko robustnější než křížové reference aktivity a fragmentů :-).
    public class DataRepository {
        private String name;
        private String email;
    
        private static DataRepository instance;
    
        public static synchronized DataRepository getInstance() {
            if (instance == null) {
                instance = new DataRepository();
            }
    
            return instance;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    }

    První fragment:
    DataRepository.getInstance().setName("Jarda");

    Druhý fragment:
    DataRepository.getInstance().setEmail("jarda@novak.cz");

    Třetí fragment:
    DataRepository dataRepository = DataRepository.getInstance();
    String name = dataRepository.getName();
    String email = dataRepository.getEmail();

    Jednoduchý jak žebřík :-).
    JOHNY_G
    JOHNY_G --- ---
    ADAMH: No už se někam dostáváme :-). Tyto fragmenty rozhodně mohou být zcela nezávislé. Potřebuješ jenom nějaký repozitář pro data. Pokud mají být perzistentní i do dalšího spuštění, tak si to dej prostě do SharedPreferences (sice potřebují context, ale na to právě můžeš použít getActivity()), odkud si to na druhé straně zase vytáhneš. Počítám tedy, že jde o triviální data, která můžeš zaznamenat jako key-value. Jinak totéž, ale s nějakou databází :-)). Pokud jsou data jednorázová a po ukončení irelevantní, tak si prostě udělej singleton, do kterého to budeš ukládat in-memory. Jestli použiješ nějaký sofistikovaný thread-safe a lifecycle-aware framework, anebo si tam prostě uděláš statickou referenci a getInstance(), to záleží čistě na tobě :-)). Osobně bych si kvůli modularitě udělal singletonový wrapper i na ty Shared Preferences, ale to už bys musel být lifecycle-aware kvůli tomu kontextu. Takže na vlastní nebezpečí :-).
    ADAMH
    ADAMH --- ---
    JOHNY_G: Je to asi dost poznat :)

    K příkladu co chci docílit. Mám svoji api třídu, která získává data z internetu či je tam ukládá.

    - intro fragment - který přes api získá data, jakmile je získá zobrazí část těchto dat a tlačítko pokračovat
    - home fragment - tam se zobrazí volby (switche) co si uzivatel nastavi a pak stiskne tlačitko na začít pracovat
    - pracovni fragment - tam se to cele provadí na zaklade dat z api tj intro fragmentu a i na zaklade toho co si nastavi uzivatel v home fragmentu pres ty switche a posleze se pres api neco ulozi

    To jak jsi to psal mě začíná případat logické, ale představoval bych si (což je asi špatně), že ta data budu mít uložena v aktivite a fragmenty je budou menit ci doplnovat dle potřeby.

    Předpokládám teda, že správně to je tak ze z activity se do fragmentu pošle v zásadě jen úvodní dávka informací při otevření a to je vše. A do activity se vicemene nic neposila.
    JOHNY_G
    JOHNY_G --- ---
    ADAMH: Já jsem se ztratil taky. Předpokládám, že Android není zrovna tvoje domovina, stejně jako Java, takže jsem zabloudil už u deskriptoru :-)). Předpokládám, že mluvíme o referenci, což už takhle na začátku vypadá na křehkou architekturu, protože fragment a aktivita nemají dokonale synchronizovaný životní cyklus, takže si koleduješ o IllegalStateException, navíc ostré reference na context (v tomto případě aktivita) nevyhnutelně vedou k memory leakům. Aktivita by měla řídit jen životní cyklus, a sice na úrovni aktivity, tedy nad fragmentem (např. spouštím jinou aktivitu, jinou aplikaci, zobrazuji dialog). Fragment by měl být soběstačný, a když po aktivitě něco chce, má na ní nullable getter getActivity(), popř. unsafe non-null requireActivity(). Nemyslím si, že by aktivita měla kdy něco chtít po fragmentu (vstupní parametry obstarají Safe Args v navigation components, popř. getInstance() a arguments bundle), tudíž neshledávám důvod k obousměrné komunikaci. Zejména, když používáš nav controller, pro který si fragment umí sáhnout sám přes findNavController(). Počítám, že MVVM nepoužíváš, takže budeš mít ve fragmentu i business logiku a tudíž ti odpadá ta relevantní oboustranná komunikace mezi fragmentem a viewmodelem. Můžeš uvést příklad svého use case? Vyzkoušený tutorial nemám, protože jsem se to učil při práci na již navrženým projektu postaveném na Android Clean Architecture (extrémně nedoporučuji!) a já jsem si z ní pro svoje projekty odnesl jen to, co jsem považoval za funkční :-).
    ADAMH
    ADAMH --- ---
    JOHNY_G: Ač nejsem začátečník, tak uznám, že se v tom ztrácím. Já si zpravidla do fragmentu přenesu deskriptor activity z activity volam přes deskriptor fragmentu. S navcontrolerem se to zdá být obtížnější. Neřeším znovpoužitelnost aplikace ale to aby fungovala.

    Bohužel těch návodu na internetu je hodně, ale je to jak kdy kotlin či java (dělám v jave), často je to návod na vieparger, často jen na nějaký základní přenosy dat a jen jedním směrem. Pro mě chaos.

    Nemáš náhodou někde opravdu funkční ukázku s navcontrolerem v jave na obousmernou komunikaci?
    JOHNY_G
    JOHNY_G --- ---
    Já s fragmenty nemám problém. Používám je s architecture a navigation components k takřka plné spokojenosti. Jediný problém mám s nav controllerem a jeho správou back stacku, ale člověk si zvykne. V MVVM není obvykle nad rámec občasných argumentů (které nav controller podporuje) ke komunikaci mezi fragmenty důvod. Na logiku přesahující potřeby obrazovky/fragmentu/viewmodelu typicky používám nějaký provider (dle potřeby singletonový nebo instancovaný přes factory) a/nebo repository.
    Kliknutím sem můžete změnit nastavení reklam