I am looking for the best way to migrate an Android app to Flutter. Sure, I could simply develop everything again from scratch, but that seems way to inefficient.
Are there any best practices on how to efficiently migrate/redevelop an existing Android app to Flutter?
More precisely, I am looking for something concerning this:
public class CheckboxListViewModel {
public String name;
public int value;
public CheckboxListViewModel(String name, int value) {
this.name = name;
this.value = value;
}
public String getName() {
return this.name;
}
public int getValue() {
return this.value;
}
public void setValue(int value) {
this.value = value;
}
public void setName(String name) {
this.name = name;
}
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/backgroundColor_fragments"
>
<ImageView
android:id="@+id/proceedingCausesAppStateToBeGone_imageView"
android:src="@drawable/ic_warning"
android:layout_centerVertical="true"
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_marginLeft="20dp"
android:layout_marginStart="20dp" />
<TextView
android:id="@+id/proceedingCausesAppStateToBeGone_textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="10dp"
android:layout_marginRight="20dp"
android:layout_marginEnd="20dp"
android:layout_marginLeft="10dp"
android:layout_marginStart="10dp"
android:gravity="center"
android:layout_toRightOf="@+id/proceedingCausesAppStateToBeGone_imageView"
android:layout_toEndOf="@+id/proceedingCausesAppStateToBeGone_imageView"
android:text="@string/appSettings_exportImport_appState_import_proceedingCausesAppStateToBeGone_DialogText"
android:textAlignment="center"
android:textColor="@color/textColor"
android:textSize="18sp"/>
</RelativeLayout>
package otterfoxxy.motivationsapp.activities;
import com.google.android.material.bottomnavigation.BottomNavigationItemView;
import com.google.android.material.bottomnavigation.BottomNavigationMenuView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.navigation.NavigationView;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.appcompat.widget.Toolbar;
public class AboutScreen extends NavigationDrawerActivity {
@Override
protected void onCreate(...)
@Override
protected void onResume()
@Override
public boolean onOptionsItemSelected(MenuItem item)
@Override
public boolean onCreateOptionsMenu(...)
@Override
public boolean onKeyUp(...)
@Override
public void onStop()
}
package otterfoxxy.motivationsapp.RoomDatabase_Diary;
/**
* Created by Patrick on 22.01.2018.
*/
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.migration.Migration;
import android.content.Context;
@Database(entities = {Entity_Diary.class, Entity_Tag.class, Entity_Diary_Tag_Relation.class}, version = 9)
public abstract class Database_Diary extends RoomDatabase {
private static final String DB_NAME = "database_diary.db";
private static volatile otterfoxxy.motivationsapp.RoomDatabase_Diary.Database_Diary instance;
static synchronized otterfoxxy.motivationsapp.RoomDatabase_Diary.Database_Diary getInstance(Context context) {
if (instance == null) {
instance = create(context);
}
return instance;
}
private static otterfoxxy.motivationsapp.RoomDatabase_Diary.Database_Diary create(final Context context) {
return Room.databaseBuilder(
context,
otterfoxxy.motivationsapp.RoomDatabase_Diary.Database_Diary.class,
DB_NAME).allowMainThreadQueries()
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9)
.build();
}
public abstract DAO_Diary get_DAO_Diary();
public abstract DAO_Tag get_DAO_Tag();
public abstract DAO_Diary_Tag_Relation get_DAO_Diary_Tag_Relation();
private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE Entity_Diary "
+ " ADD hasImageDiaryEntry INTEGER DEFAULT 0;");
database.execSQL("ALTER TABLE Entity_Diary "
+ " ADD imageDiaryEntryFilenameOrUri TEXT DEFAULT \"\" ;");
database.execSQL("ALTER TABLE Entity_Diary "
+ " ADD imageDiaryEntryType INTEGER NOT NULL DEFAULT 0;");
}
};
private static final Migration MIGRATION_2_3 = new Migration(2, 3) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE IF NOT EXISTS `Entity_Tag` (`tagID` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `description` TEXT , `iconID` INTEGER NOT NULL, `position` INTEGER NOT NULL)");
database.execSQL("CREATE TABLE IF NOT EXISTS `Entity_Diary_Tag_Relation` (`diaryID` INTEGER NOT NULL, `tagID` INTEGER NOT NULL, PRIMARY KEY (diaryID, tagID), FOREIGN KEY(`diaryID`) REFERENCES `Entity_Diary`(`diaryID`) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY(`tagID`) REFERENCES `Entity_Tag`(`tagID`) ON UPDATE NO ACTION ON DELETE CASCADE )");
}
};
private static final Migration MIGRATION_3_4 = new Migration(3, 4) {
@Override
public void migrate(SupportSQLiteDatabase database) {
}
};
private static final Migration MIGRATION_4_5 = new Migration(4, 5) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE Entity_Diary "
+ " ADD isFavourite INTEGER DEFAULT 0;");
}
};
private static final Migration MIGRATION_5_6 = new Migration(5, 6) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE Entity_Diary "
+ " ADD energy INTEGER NOT NULL DEFAULT -1;");
}
};
/**
* Invert stress.
*/
private static final Migration MIGRATION_6_7 = new Migration(6, 7) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("UPDATE Entity_Diary "
+ " SET stresslevel = (10 - stresslevel) WHERE stresslevel != -1;");
}
};
/**
* Add Tag rating.
*/
private static final Migration MIGRATION_7_8 = new Migration(7, 8) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE Entity_Tag "
+ " ADD rating INTEGER NOT NULL DEFAULT -1;");
database.execSQL("ALTER TABLE Entity_Tag "
+ " ADD mood INTEGER NOT NULL DEFAULT -1;");
database.execSQL("ALTER TABLE Entity_Tag "
+ " ADD stress INTEGER NOT NULL DEFAULT -1;");
database.execSQL("ALTER TABLE Entity_Tag "
+ " ADD energy INTEGER NOT NULL DEFAULT -1;");
}
};
private static final Migration MIGRATION_8_9 = new Migration(8, 9) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE Entity_Diary "
+ " ADD rating INTEGER NOT NULL DEFAULT -1;");
}
};
}
package otterfoxxy.motivationsapp.RoomDatabase_Diary;
import static otterfoxxy.motivationsapp.utils.FileUtils.copyFile;
import static otterfoxxy.motivationsapp.utils.FileUtils.getFileExtensionFromPathWithoutLeadingDot;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
import android.widget.Toast;
import androidx.documentfile.provider.DocumentFile;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import otterfoxxy.motivationsapp.R;
import otterfoxxy.motivationsapp.utils.DiaryUtils;
import otterfoxxy.motivationsapp.utils.StringUtils;
/**
* Created by Patrick on 22.01.2018.
*/
public class DatabaseDiary_Wrapper {
private Context context;
private DAO_Diary dao_diary;
private DAO_Tag dao_tag;
private DAO_Diary_Tag_Relation dao_diary_tag_relation;
/*
constructor
*/
public DatabaseDiary_Wrapper(Context context) {
this.context = context;
dao_diary = Database_Diary
.getInstance(context)
.get_DAO_Diary();
dao_tag = Database_Diary
.getInstance(context)
.get_DAO_Tag();
dao_diary_tag_relation = Database_Diary
.getInstance(context)
.get_DAO_Diary_Tag_Relation();
}
/**
* !!POTENTIAL DANGEROUS METHOD!!! Deletes diary entry.
*
* @param diaryEntryID
* ID of the diary entry to delete.
*/
public void DELETE_DIARY_ENTRY(long diaryEntryID) {
dao_diary.DELETE_DIARY_ENTRY(diaryEntryID);
}
/**
* Remove image from diary entry.
*
* @param diaryEntryID
* ID of the diary entry to update.
*/
public void removeImageFromDiaryEntry(long diaryEntryID) {
Entity_Diary entity_diary = getDiaryEntityByID(diaryEntryID);
entity_diary.hasImageDiaryEntry = false;
dao_diary.update(entity_diary);
}
...
}
<?xml version="1.0" encoding="utf-8"?>
<!-- Entities to use across this file to avoid redundancy -->
<!DOCTYPE resources [
<!ENTITY appname "Lebe Jetzt">
<!ENTITY menu_about_string "Informationen">
<!ENTITY aboutScreen_feedback_Button_Text "Feedback / Kontakt">
]>
<resources>
<!-- Global strings -->
<string name="app_name">&appname;</string>
<string name="happyMakingActivities_name">Wohltuendes</string>
...
<string-array name="dailyRating_dialogClosingTexts_badRating">
<item>Wie wirst Du den Tag beschließen?</item>
<item>Was könnte diesen Tag noch retten?</item>
<item>Was machst Du heute Abend?</item>
</string-array>
...
</resources>
to
{
"app_name": "Lebe Jetzt",
"happyMakingActivities_name": "Wohltuendes",
...
}
As of now - as stated in the comments - it appears that the only option is to manually redevelop the app.
However, if you went with a model view controller pattern you can use java to dart converter tools to recreate simple classes.
Although there will be some difficulties you can also convert xml to json using tools.
The example of the question results in this:
{
"string": [
{
"@name": "app_name",
"#text": "Lebe Jetzt"
},
{
"@name": "happyMakingActivities_name",
"#text": "Wohltuendes"
}
],
"string-array": {
"@name": "dailyRating_dialogClosingTexts_badRating",
"item": [
"Wie wirst Du den Tag beschließen?",
"Was könnte diesen Tag noch retten?",
"Was machst Du heute Abend?"
]
}
}